|
64658
|
2264
|
21
|
2026-05-20T14:10:37.360370+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779286237360_m1.jpg...
|
Firefox
|
JY-20676 delete AJ reports related objects by Laky JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app — Work...
|
1
|
github.com/jiminny/app/pull/12098
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Please fill out this field.
Platform Sprint 4 Q2 - Please fill out this field.
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:...
|
[{"role":"AXStaticText","text& [{"role":"AXStaticText","text":"Please fill out this field.","depth":2,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Main menu","depth":13,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New chat","depth":12,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Chat","depth":13,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":13,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"You said I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a concise summary of the changes made in the code selection:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Refactoring and Dependencies","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Refactoring and Dependencies","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Replaced Interface with Implementation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusInterface","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusManager","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in the imports and constructor dependency injection.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Redis Support:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Imported","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Illuminate\\Support\\Facades\\Redis","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to handle a newly introduced memory-logging lock mechanism.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Traits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WorkerIdTrait","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", along with the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"worker","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"field tracking in the final logs.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Loop and Worker Lifecycle Adjustments","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Loop and Worker Lifecycle Adjustments","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Worker Auto-Restart:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Deleted the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property, the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"constant, and the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"shouldIterateAgain()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method. The script no longer tracks execution iterations or triggers a self-managed worker restart.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Simplified Chunk Method Return:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Modified","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to always return","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Priority Handling for Throttled Entities","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Priority Handling for Throttled Entities","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Stale Record Elevation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Updated the logic in","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"rescheduleThrottledEntities()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to check if a throttled entity group is classified as \"stale\". If it is, the entities are rescheduled using","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"QueuePriorityEnum::NORMAL","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"; otherwise, they keep their respective queue priority.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Metrics, Logging, and Environment Handling","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Metrics, Logging, and Environment Handling","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Environment Tracking:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Introduced a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$environment","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property (defaulting to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'local'","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") and a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"setEnvironment()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method to dynamically assign it.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Memory Monitoring via Datadog Gauge:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method that submits peak memory allocation to Datadog as a gauge metric (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"jiminny.reindex-memory-usage","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis Lock for Metrics Throttling:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Implemented a Redis-backed lock (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"%s-process-manager-lock","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") with a 60-second expiration inside","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to prevent over-reporting metrics.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Log Payload Update:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added total processed items (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$updatedDocuments + $deletedDocuments","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") to the final information log.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":23,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said are there any issues or potential bottlenecks","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"are there any issues or potential bottlenecks","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a breakdown of the potential issues to look out for:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"1. Memory Leak Risk (OOM Crashes)","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. Memory Leak Risk (OOM Crashes)","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Restart Logic:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The most significant change is the removal of the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Dependency Leaks:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If any underlying service (like the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ElasticSearchClient","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", ORM, or Datadog helper) retains references to processed entities,","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Hardcoded Limits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The code still includes","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ini_set('memory_limit', '512M');","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"2. Race Condition in Redis Lock","depth":23,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. Race Condition in Redis Lock","depth":24,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Non-Atomic Operations:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method uses three distinct steps to handle the lock:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-6391640691959655301
|
-1061874711537551460
|
visual_change
|
accessibility
|
NULL
|
Please fill out this field.
Platform Sprint 4 Q2 - Please fill out this field.
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
64659
|
2265
|
42
|
2026-05-20T14:10:39.957792+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779286239957_m2.jpg...
|
Firefox
|
JY-20676 delete AJ reports related objects by Laky JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app — Work...
|
1
|
github.com/jiminny/app/pull/12098
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage.
4. Performance Overhead
4. Performance Overhead
Expensive Garbage Collection:
Calling
gc_collect_cycles();
on
every single chunk
can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.
Synchronous Metric Dispatch:
If the
Datadog::increment
or
Datadog::gauge
methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.
5. Lack of Explicit Error Handling
5. Lack of Explicit Error Handling
No
try/catch
Blocks:
The script executes complex network operations (
loadDocuments
, Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.
Good response
Bad response
Redo
Share & export
Copy
Show more options
Ask Gemini
Ask Gemini
Upload & tools...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.10721409,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.15791224,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.1245012,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.1796875,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.12699468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":4,"bounds":{"left":0.0,"top":0.21548285,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":5,"bounds":{"left":0.013297873,"top":0.22665602,"width":0.10139628,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"bounds":{"left":0.0,"top":0.2482043,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.25937748,"width":0.18068483,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.28092578,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.29209897,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.31364724,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.32482043,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.3463687,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.3575419,"width":0.15824468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.38068634,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"bounds":{"left":0.35854387,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"bounds":{"left":0.37051198,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Main menu","depth":13,"bounds":{"left":0.08228058,"top":0.09736632,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New chat","depth":12,"bounds":{"left":0.09823803,"top":0.10215483,"width":0.028091755,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":15,"bounds":{"left":0.1008976,"top":0.10654429,"width":0.02144282,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Chat","depth":13,"bounds":{"left":0.35854387,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":13,"bounds":{"left":0.37051198,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"bounds":{"left":0.079288565,"top":0.14445332,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"bounds":{"left":0.079288565,"top":0.14684756,"width":0.1200133,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"You said I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a concise summary of the changes made in the code selection:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Refactoring and Dependencies","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Refactoring and Dependencies","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Replaced Interface with Implementation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusInterface","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusManager","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in the imports and constructor dependency injection.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Redis Support:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Imported","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Illuminate\\Support\\Facades\\Redis","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to handle a newly introduced memory-logging lock mechanism.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Traits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WorkerIdTrait","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", along with the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"worker","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"field tracking in the final logs.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Loop and Worker Lifecycle Adjustments","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Loop and Worker Lifecycle Adjustments","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Worker Auto-Restart:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Deleted the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property, the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"constant, and the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"shouldIterateAgain()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method. The script no longer tracks execution iterations or triggers a self-managed worker restart.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Simplified Chunk Method Return:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Modified","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to always return","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Priority Handling for Throttled Entities","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Priority Handling for Throttled Entities","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Stale Record Elevation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Updated the logic in","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"rescheduleThrottledEntities()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to check if a throttled entity group is classified as \"stale\". If it is, the entities are rescheduled using","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"QueuePriorityEnum::NORMAL","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"; otherwise, they keep their respective queue priority.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Metrics, Logging, and Environment Handling","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Metrics, Logging, and Environment Handling","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Environment Tracking:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Introduced a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$environment","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property (defaulting to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'local'","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") and a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"setEnvironment()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method to dynamically assign it.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Memory Monitoring via Datadog Gauge:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method that submits peak memory allocation to Datadog as a gauge metric (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"jiminny.reindex-memory-usage","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis Lock for Metrics Throttling:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Implemented a Redis-backed lock (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"%s-process-manager-lock","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") with a 60-second expiration inside","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to prevent over-reporting metrics.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Log Payload Update:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added total processed items (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$updatedDocuments + $deletedDocuments","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") to the final information log.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":23,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said are there any issues or potential bottlenecks","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"are there any issues or potential bottlenecks","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a breakdown of the potential issues to look out for:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"1. Memory Leak Risk (OOM Crashes)","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. Memory Leak Risk (OOM Crashes)","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Restart Logic:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The most significant change is the removal of the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Dependency Leaks:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If any underlying service (like the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ElasticSearchClient","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", ORM, or Datadog helper) retains references to processed entities,","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"bounds":{"left":0.26811835,"top":0.0,"width":0.056848403,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23138298,"height":0.05546688},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Hardcoded Limits:","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.047041222,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The code still includes","depth":26,"bounds":{"left":0.17154256,"top":0.0,"width":0.05651596,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ini_set('memory_limit', '512M');","depth":27,"bounds":{"left":0.23005319,"top":0.0,"width":0.095744684,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23055187,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"2. Race Condition in Redis Lock","depth":23,"bounds":{"left":0.111369684,"top":0.06823623,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. Race Condition in Redis Lock","depth":24,"bounds":{"left":0.111369684,"top":0.06783719,"width":0.09125665,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Non-Atomic Operations:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.062333778,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The","depth":26,"bounds":{"left":0.18683511,"top":0.09497207,"width":0.011635638,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage","depth":27,"bounds":{"left":0.20046543,"top":0.096568234,"width":0.041888297,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method uses three distinct steps to handle the lock:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.22722739,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"get()","depth":27,"bounds":{"left":0.14860372,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":26,"bounds":{"left":0.16555852,"top":0.11412609,"width":0.002493351,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.17004654,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", and","depth":26,"bounds":{"left":0.18700133,"top":0.11412609,"width":0.013131649,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"expire()","depth":27,"bounds":{"left":0.20212767,"top":0.11572227,"width":0.023936171,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.22805852,"top":0.11412609,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If multiple workers process the same","depth":26,"bounds":{"left":0.16456117,"top":0.14285715,"width":0.09275266,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$this->updateTarget","depth":27,"bounds":{"left":0.25930852,"top":0.14445332,"width":0.056848403,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"concurrently, they can both pass the","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"if (! is_null(Redis::get(...)))","depth":27,"bounds":{"left":0.18351063,"top":0.16360734,"width":0.09275266,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"check at the exact same millisecond. Both will then execute","depth":26,"bounds":{"left":0.12450133,"top":0.16201118,"width":0.21575798,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.2122673,"top":0.18276137,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and push duplicate metrics to Datadog.","depth":26,"bounds":{"left":0.22922207,"top":0.1811652,"width":0.09823803,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Fix:","depth":26,"bounds":{"left":0.12450133,"top":0.20989625,"width":0.019281914,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This should be replaced with a single atomic operation using Redis's","depth":26,"bounds":{"left":0.14378324,"top":0.20989625,"width":0.16821809,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"NX","depth":27,"bounds":{"left":0.31399602,"top":0.21149242,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Not eXists) and","depth":26,"bounds":{"left":0.12450133,"top":0.20989625,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"EX","depth":27,"bounds":{"left":0.13696809,"top":0.23064645,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Expire) flags, or Laravel’s built-in atomic cache locks:","depth":26,"bounds":{"left":0.14494681,"top":0.22905028,"width":0.13264628,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis::set($gaugeLockName, true, 'EX', 60, 'NX')","depth":27,"bounds":{"left":0.12649602,"top":0.24980047,"width":0.14361702,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.27210772,"top":0.2482043,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"3. Infinite Loops and Caller Contract","depth":23,"bounds":{"left":0.111369684,"top":0.2980846,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. Infinite Loops and Caller Contract","depth":24,"bounds":{"left":0.111369684,"top":0.29768556,"width":0.10571808,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Return Value Change:","depth":26,"bounds":{"left":0.12450133,"top":0.32482043,"width":0.055352394,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"bounds":{"left":0.18301196,"top":0.3264166,"width":0.06881649,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"previously returned whether it should iterate again based on staleness or chunk size. It now always returns","depth":26,"bounds":{"left":0.12450133,"top":0.32482043,"width":0.2237367,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"bounds":{"left":0.29438165,"top":0.34557062,"width":0.011968086,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(unless the selection is totally empty).","depth":26,"bounds":{"left":0.12450133,"top":0.34397447,"width":0.2130984,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.39185953,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the code calling this method relies on a","depth":26,"bounds":{"left":0.16456117,"top":0.39185953,"width":0.10305851,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"false","depth":27,"bounds":{"left":0.26961437,"top":0.3934557,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"return to pause, sleep, or gracefully terminate, changing this to an unconditional","depth":26,"bounds":{"left":0.12450133,"top":0.39185953,"width":0.22589761,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"bounds":{"left":0.26163563,"top":0.41260973,"width":0.011968086,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"could result in an aggressive \"busy loop\" that spikes CPU usage.","depth":26,"bounds":{"left":0.12450133,"top":0.41101357,"width":0.22290559,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"4. Performance Overhead","depth":23,"bounds":{"left":0.111369684,"top":0.48004788,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4. Performance Overhead","depth":24,"bounds":{"left":0.111369684,"top":0.47964883,"width":0.076130316,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Expensive Garbage Collection:","depth":26,"bounds":{"left":0.12450133,"top":0.5067837,"width":0.078457445,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Calling","depth":26,"bounds":{"left":0.20295878,"top":0.5067837,"width":0.019281914,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles();","depth":27,"bounds":{"left":0.22423537,"top":0.5083799,"width":0.059840426,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"on","depth":26,"bounds":{"left":0.28607047,"top":0.5067837,"width":0.00880984,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"every single chunk","depth":26,"bounds":{"left":0.29488033,"top":0.5067837,"width":0.045545213,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.","depth":26,"bounds":{"left":0.12450133,"top":0.5067837,"width":0.23420878,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Synchronous Metric Dispatch:","depth":26,"bounds":{"left":0.12450133,"top":0.5738228,"width":0.07712766,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the","depth":26,"bounds":{"left":0.20162898,"top":0.5738228,"width":0.015292553,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Datadog::increment","depth":27,"bounds":{"left":0.21891622,"top":0.575419,"width":0.053856384,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"or","depth":26,"bounds":{"left":0.27476728,"top":0.5738228,"width":0.0078125,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Datadog::gauge","depth":27,"bounds":{"left":0.28457448,"top":0.575419,"width":0.041888297,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.","depth":26,"bounds":{"left":0.12450133,"top":0.5738228,"width":0.23420878,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"5. Lack of Explicit Error Handling","depth":23,"bounds":{"left":0.111369684,"top":0.66201115,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5. Lack of Explicit Error Handling","depth":24,"bounds":{"left":0.111369684,"top":0.66161215,"width":0.094913565,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No","depth":26,"bounds":{"left":0.12450133,"top":0.688747,"width":0.008477394,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"try/catch","depth":27,"bounds":{"left":0.1349734,"top":0.6903432,"width":0.026928192,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Blocks:","depth":26,"bounds":{"left":0.16389628,"top":0.688747,"width":0.019614361,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The script executes complex network operations (","depth":26,"bounds":{"left":0.18351063,"top":0.688747,"width":0.12367021,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"loadDocuments","depth":27,"bounds":{"left":0.30917552,"top":0.6903432,"width":0.038896278,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.","depth":26,"bounds":{"left":0.12450133,"top":0.688747,"width":0.22888963,"height":0.09377494},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":22,"bounds":{"left":0.1087101,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":22,"bounds":{"left":0.124667555,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Redo","depth":22,"bounds":{"left":0.140625,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":21,"bounds":{"left":0.15658244,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":22,"bounds":{"left":0.17253989,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":21,"bounds":{"left":0.18849733,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXTextArea","text":"Ask Gemini","depth":21,"bounds":{"left":0.14594415,"top":0.86632085,"width":0.17021276,"height":0.01915403},"on_screen":true,"value":"Ask Gemini","help_text":"","role_description":"text entry area","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Ask Gemini","depth":22,"bounds":{"left":0.14594415,"top":0.8667199,"width":0.030418882,"height":0.018355945},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Upload & tools","depth":20,"bounds":{"left":0.12732713,"top":0.8567438,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-8954306563373950343
|
-1070952279536486628
|
visual_change
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage.
4. Performance Overhead
4. Performance Overhead
Expensive Garbage Collection:
Calling
gc_collect_cycles();
on
every single chunk
can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.
Synchronous Metric Dispatch:
If the
Datadog::increment
or
Datadog::gauge
methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.
5. Lack of Explicit Error Handling
5. Lack of Explicit Error Handling
No
try/catch
Blocks:
The script executes complex network operations (
loadDocuments
, Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.
Good response
Bad response
Redo
Share & export
Copy
Show more options
Ask Gemini
Ask Gemini
Upload & tools...
|
64656
|
NULL
|
NULL
|
NULL
|
|
64660
|
2265
|
43
|
2026-05-20T14:10:43.181919+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779286243181_m2.jpg...
|
Firefox
|
JY-20676 delete AJ reports related objects by Laky JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app — Work...
|
1
|
github.com/jiminny/app/pull/12098
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage.
4. Performance Overhead
4. Performance Overhead
Expensive Garbage Collection:
Calling
gc_collect_cycles();
on
every single chunk
can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.
Synchronous Metric Dispatch:
If the
Datadog::increment
or
Datadog::gauge
methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.
5. Lack of Explicit Error Handling
5. Lack of Explicit Error Handling
No
try/catch
Blocks:
The script executes complex network operations (
loadDocuments
, Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.
Good response
Bad response
Redo...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.10721409,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.15791224,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.1245012,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.1796875,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.12699468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":4,"bounds":{"left":0.0,"top":0.21548285,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":5,"bounds":{"left":0.013297873,"top":0.22665602,"width":0.10139628,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"bounds":{"left":0.0,"top":0.2482043,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.25937748,"width":0.18068483,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.28092578,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.29209897,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.31364724,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.32482043,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.3463687,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.3575419,"width":0.15824468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.38068634,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"bounds":{"left":0.35854387,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"bounds":{"left":0.37051198,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Main menu","depth":13,"bounds":{"left":0.08228058,"top":0.09736632,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New chat","depth":12,"bounds":{"left":0.09823803,"top":0.10215483,"width":0.028091755,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":15,"bounds":{"left":0.1008976,"top":0.10654429,"width":0.02144282,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Chat","depth":13,"bounds":{"left":0.35854387,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":13,"bounds":{"left":0.37051198,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"bounds":{"left":0.079288565,"top":0.14445332,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"bounds":{"left":0.079288565,"top":0.14684756,"width":0.1200133,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"You said I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a concise summary of the changes made in the code selection:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Refactoring and Dependencies","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Refactoring and Dependencies","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Replaced Interface with Implementation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusInterface","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusManager","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in the imports and constructor dependency injection.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Redis Support:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Imported","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Illuminate\\Support\\Facades\\Redis","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to handle a newly introduced memory-logging lock mechanism.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Traits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WorkerIdTrait","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", along with the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"worker","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"field tracking in the final logs.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Loop and Worker Lifecycle Adjustments","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Loop and Worker Lifecycle Adjustments","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Worker Auto-Restart:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Deleted the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property, the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"constant, and the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"shouldIterateAgain()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method. The script no longer tracks execution iterations or triggers a self-managed worker restart.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Simplified Chunk Method Return:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Modified","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to always return","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Priority Handling for Throttled Entities","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Priority Handling for Throttled Entities","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Stale Record Elevation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Updated the logic in","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"rescheduleThrottledEntities()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to check if a throttled entity group is classified as \"stale\". If it is, the entities are rescheduled using","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"QueuePriorityEnum::NORMAL","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"; otherwise, they keep their respective queue priority.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Metrics, Logging, and Environment Handling","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Metrics, Logging, and Environment Handling","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Environment Tracking:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Introduced a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$environment","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property (defaulting to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'local'","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") and a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"setEnvironment()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method to dynamically assign it.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Memory Monitoring via Datadog Gauge:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method that submits peak memory allocation to Datadog as a gauge metric (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"jiminny.reindex-memory-usage","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis Lock for Metrics Throttling:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Implemented a Redis-backed lock (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"%s-process-manager-lock","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") with a 60-second expiration inside","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to prevent over-reporting metrics.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Log Payload Update:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added total processed items (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$updatedDocuments + $deletedDocuments","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") to the final information log.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":23,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said are there any issues or potential bottlenecks","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"are there any issues or potential bottlenecks","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a breakdown of the potential issues to look out for:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"1. Memory Leak Risk (OOM Crashes)","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. Memory Leak Risk (OOM Crashes)","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Restart Logic:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The most significant change is the removal of the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Dependency Leaks:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If any underlying service (like the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ElasticSearchClient","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", ORM, or Datadog helper) retains references to processed entities,","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"bounds":{"left":0.26811835,"top":0.0,"width":0.056848403,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23138298,"height":0.05546688},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Hardcoded Limits:","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.047041222,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The code still includes","depth":26,"bounds":{"left":0.17154256,"top":0.0,"width":0.05651596,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ini_set('memory_limit', '512M');","depth":27,"bounds":{"left":0.23005319,"top":0.0,"width":0.095744684,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23055187,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"2. Race Condition in Redis Lock","depth":23,"bounds":{"left":0.111369684,"top":0.06823623,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. Race Condition in Redis Lock","depth":24,"bounds":{"left":0.111369684,"top":0.06783719,"width":0.09125665,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Non-Atomic Operations:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.062333778,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The","depth":26,"bounds":{"left":0.18683511,"top":0.09497207,"width":0.011635638,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage","depth":27,"bounds":{"left":0.20046543,"top":0.096568234,"width":0.041888297,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method uses three distinct steps to handle the lock:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.22722739,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"get()","depth":27,"bounds":{"left":0.14860372,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":26,"bounds":{"left":0.16555852,"top":0.11412609,"width":0.002493351,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.17004654,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", and","depth":26,"bounds":{"left":0.18700133,"top":0.11412609,"width":0.013131649,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"expire()","depth":27,"bounds":{"left":0.20212767,"top":0.11572227,"width":0.023936171,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.22805852,"top":0.11412609,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If multiple workers process the same","depth":26,"bounds":{"left":0.16456117,"top":0.14285715,"width":0.09275266,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$this->updateTarget","depth":27,"bounds":{"left":0.25930852,"top":0.14445332,"width":0.056848403,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"concurrently, they can both pass the","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"if (! is_null(Redis::get(...)))","depth":27,"bounds":{"left":0.18351063,"top":0.16360734,"width":0.09275266,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"check at the exact same millisecond. Both will then execute","depth":26,"bounds":{"left":0.12450133,"top":0.16201118,"width":0.21575798,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.2122673,"top":0.18276137,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and push duplicate metrics to Datadog.","depth":26,"bounds":{"left":0.22922207,"top":0.1811652,"width":0.09823803,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Fix:","depth":26,"bounds":{"left":0.12450133,"top":0.20989625,"width":0.019281914,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This should be replaced with a single atomic operation using Redis's","depth":26,"bounds":{"left":0.14378324,"top":0.20989625,"width":0.16821809,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"NX","depth":27,"bounds":{"left":0.31399602,"top":0.21149242,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Not eXists) and","depth":26,"bounds":{"left":0.12450133,"top":0.20989625,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"EX","depth":27,"bounds":{"left":0.13696809,"top":0.23064645,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Expire) flags, or Laravel’s built-in atomic cache locks:","depth":26,"bounds":{"left":0.14494681,"top":0.22905028,"width":0.13264628,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis::set($gaugeLockName, true, 'EX', 60, 'NX')","depth":27,"bounds":{"left":0.12649602,"top":0.24980047,"width":0.14361702,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.27210772,"top":0.2482043,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"3. Infinite Loops and Caller Contract","depth":23,"bounds":{"left":0.111369684,"top":0.2980846,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. Infinite Loops and Caller Contract","depth":24,"bounds":{"left":0.111369684,"top":0.29768556,"width":0.10571808,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Return Value Change:","depth":26,"bounds":{"left":0.12450133,"top":0.32482043,"width":0.055352394,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"bounds":{"left":0.18301196,"top":0.3264166,"width":0.06881649,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"previously returned whether it should iterate again based on staleness or chunk size. It now always returns","depth":26,"bounds":{"left":0.12450133,"top":0.32482043,"width":0.2237367,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"bounds":{"left":0.29438165,"top":0.34557062,"width":0.011968086,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(unless the selection is totally empty).","depth":26,"bounds":{"left":0.12450133,"top":0.34397447,"width":0.2130984,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.39185953,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the code calling this method relies on a","depth":26,"bounds":{"left":0.16456117,"top":0.39185953,"width":0.10305851,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"false","depth":27,"bounds":{"left":0.26961437,"top":0.3934557,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"return to pause, sleep, or gracefully terminate, changing this to an unconditional","depth":26,"bounds":{"left":0.12450133,"top":0.39185953,"width":0.22589761,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"bounds":{"left":0.26163563,"top":0.41260973,"width":0.011968086,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"could result in an aggressive \"busy loop\" that spikes CPU usage.","depth":26,"bounds":{"left":0.12450133,"top":0.41101357,"width":0.22290559,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"4. Performance Overhead","depth":23,"bounds":{"left":0.111369684,"top":0.48004788,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4. Performance Overhead","depth":24,"bounds":{"left":0.111369684,"top":0.47964883,"width":0.076130316,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Expensive Garbage Collection:","depth":26,"bounds":{"left":0.12450133,"top":0.5067837,"width":0.078457445,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Calling","depth":26,"bounds":{"left":0.20295878,"top":0.5067837,"width":0.019281914,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles();","depth":27,"bounds":{"left":0.22423537,"top":0.5083799,"width":0.059840426,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"on","depth":26,"bounds":{"left":0.28607047,"top":0.5067837,"width":0.00880984,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"every single chunk","depth":26,"bounds":{"left":0.29488033,"top":0.5067837,"width":0.045545213,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.","depth":26,"bounds":{"left":0.12450133,"top":0.5067837,"width":0.23420878,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Synchronous Metric Dispatch:","depth":26,"bounds":{"left":0.12450133,"top":0.5738228,"width":0.07712766,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the","depth":26,"bounds":{"left":0.20162898,"top":0.5738228,"width":0.015292553,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Datadog::increment","depth":27,"bounds":{"left":0.21891622,"top":0.575419,"width":0.053856384,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"or","depth":26,"bounds":{"left":0.27476728,"top":0.5738228,"width":0.0078125,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Datadog::gauge","depth":27,"bounds":{"left":0.28457448,"top":0.575419,"width":0.041888297,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.","depth":26,"bounds":{"left":0.12450133,"top":0.5738228,"width":0.23420878,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"5. Lack of Explicit Error Handling","depth":23,"bounds":{"left":0.111369684,"top":0.66201115,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5. Lack of Explicit Error Handling","depth":24,"bounds":{"left":0.111369684,"top":0.66161215,"width":0.094913565,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No","depth":26,"bounds":{"left":0.12450133,"top":0.688747,"width":0.008477394,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"try/catch","depth":27,"bounds":{"left":0.1349734,"top":0.6903432,"width":0.026928192,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Blocks:","depth":26,"bounds":{"left":0.16389628,"top":0.688747,"width":0.019614361,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The script executes complex network operations (","depth":26,"bounds":{"left":0.18351063,"top":0.688747,"width":0.12367021,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"loadDocuments","depth":27,"bounds":{"left":0.30917552,"top":0.6903432,"width":0.038896278,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.","depth":26,"bounds":{"left":0.12450133,"top":0.688747,"width":0.22888963,"height":0.09377494},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":22,"bounds":{"left":0.1087101,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":22,"bounds":{"left":0.124667555,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Redo","depth":22,"bounds":{"left":0.140625,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
2862039115560544110
|
-1052937846667266084
|
click
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage.
4. Performance Overhead
4. Performance Overhead
Expensive Garbage Collection:
Calling
gc_collect_cycles();
on
every single chunk
can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.
Synchronous Metric Dispatch:
If the
Datadog::increment
or
Datadog::gauge
methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.
5. Lack of Explicit Error Handling
5. Lack of Explicit Error Handling
No
try/catch
Blocks:
The script executes complex network operations (
loadDocuments
, Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.
Good response
Bad response
Redo...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
64661
|
2265
|
44
|
2026-05-20T14:10:43.933422+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779286243933_m2.jpg...
|
Firefox
|
JY-20676 delete AJ reports related objects by Laky JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app — Work...
|
1
|
github.com/jiminny/app/pull/12098
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage.
4. Performance Overhead
4. Performance Overhead
Expensive Garbage Collection:
Calling
gc_collect_cycles();
on
every single chunk
can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.
Synchronous Metric Dispatch:
If the
Datadog::increment
or
Datadog::gauge
methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.
5. Lack of Explicit Error Handling
5. Lack of Explicit Error Handling
No
try/catch
Blocks:
The script executes complex network operations (
loadDocuments
, Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.
Good response
Bad response
Redo
Share & export
Copy
Show more options
Ask Gemini
Ask Gemini
Upload & tools
Microphone
Send message
Your Jiminny chats aren’t used to improve our models. Gemini is AI. It can make mistakes, so double check it.
Your privacy & Gemini Opens in a new window
Your privacy & Gemini
Opens in a new window
Summarize page
Summarize page
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
app
app
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
All issues(g then i)
All pull requests
All repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Pull requests (31)
Pull requests
(
31
)
Agents
Agents
Actions
Actions
Wiki
Wiki
Security and quality
Security and quality
Insights
Insights
Settings
Settings
JY-20676 delete AJ reports related objects #12098 Edit title
JY-20676 delete AJ reports related objects
#
12098
Edit title
Checks pending
Checks pending
Code
Code
Open
LakyLak
LakyLak
wants to merge 20 commits into
master
master
from
JY-20676-delete-report-related-objects
JY-20676-delete-report-related-objects
Copy head branch name to clipboard
Lines changed: 1698 additions & 1471 deletions
Conversation (9)
Conversation
(...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.10721409,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.15791224,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.1245012,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.1796875,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.12699468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":4,"bounds":{"left":0.0,"top":0.21548285,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":5,"bounds":{"left":0.013297873,"top":0.22665602,"width":0.10139628,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"bounds":{"left":0.0,"top":0.2482043,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.25937748,"width":0.18068483,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.28092578,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.29209897,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.31364724,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.32482043,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.3463687,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.3575419,"width":0.15824468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.38068634,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"bounds":{"left":0.35854387,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"bounds":{"left":0.37051198,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Main menu","depth":13,"bounds":{"left":0.08228058,"top":0.09736632,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New chat","depth":12,"bounds":{"left":0.09823803,"top":0.10215483,"width":0.028091755,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":15,"bounds":{"left":0.1008976,"top":0.10654429,"width":0.02144282,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Chat","depth":13,"bounds":{"left":0.35854387,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":13,"bounds":{"left":0.37051198,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"bounds":{"left":0.079288565,"top":0.14445332,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"bounds":{"left":0.079288565,"top":0.14684756,"width":0.1200133,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"You said I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a concise summary of the changes made in the code selection:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Refactoring and Dependencies","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Refactoring and Dependencies","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Replaced Interface with Implementation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusInterface","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusManager","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in the imports and constructor dependency injection.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Redis Support:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Imported","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Illuminate\\Support\\Facades\\Redis","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to handle a newly introduced memory-logging lock mechanism.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Traits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WorkerIdTrait","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", along with the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"worker","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"field tracking in the final logs.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Loop and Worker Lifecycle Adjustments","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Loop and Worker Lifecycle Adjustments","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Worker Auto-Restart:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Deleted the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property, the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"constant, and the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"shouldIterateAgain()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method. The script no longer tracks execution iterations or triggers a self-managed worker restart.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Simplified Chunk Method Return:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Modified","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to always return","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Priority Handling for Throttled Entities","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Priority Handling for Throttled Entities","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Stale Record Elevation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Updated the logic in","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"rescheduleThrottledEntities()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to check if a throttled entity group is classified as \"stale\". If it is, the entities are rescheduled using","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"QueuePriorityEnum::NORMAL","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"; otherwise, they keep their respective queue priority.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Metrics, Logging, and Environment Handling","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Metrics, Logging, and Environment Handling","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Environment Tracking:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Introduced a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$environment","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property (defaulting to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'local'","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") and a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"setEnvironment()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method to dynamically assign it.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Memory Monitoring via Datadog Gauge:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method that submits peak memory allocation to Datadog as a gauge metric (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"jiminny.reindex-memory-usage","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis Lock for Metrics Throttling:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Implemented a Redis-backed lock (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"%s-process-manager-lock","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") with a 60-second expiration inside","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to prevent over-reporting metrics.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Log Payload Update:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added total processed items (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$updatedDocuments + $deletedDocuments","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") to the final information log.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":23,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said are there any issues or potential bottlenecks","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"are there any issues or potential bottlenecks","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a breakdown of the potential issues to look out for:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"1. Memory Leak Risk (OOM Crashes)","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. Memory Leak Risk (OOM Crashes)","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Restart Logic:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The most significant change is the removal of the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Dependency Leaks:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If any underlying service (like the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ElasticSearchClient","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", ORM, or Datadog helper) retains references to processed entities,","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"bounds":{"left":0.26811835,"top":0.0,"width":0.056848403,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23138298,"height":0.05546688},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Hardcoded Limits:","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.047041222,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The code still includes","depth":26,"bounds":{"left":0.17154256,"top":0.0,"width":0.05651596,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ini_set('memory_limit', '512M');","depth":27,"bounds":{"left":0.23005319,"top":0.0,"width":0.095744684,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23055187,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"2. Race Condition in Redis Lock","depth":23,"bounds":{"left":0.111369684,"top":0.06823623,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. Race Condition in Redis Lock","depth":24,"bounds":{"left":0.111369684,"top":0.06783719,"width":0.09125665,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Non-Atomic Operations:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.062333778,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The","depth":26,"bounds":{"left":0.18683511,"top":0.09497207,"width":0.011635638,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage","depth":27,"bounds":{"left":0.20046543,"top":0.096568234,"width":0.041888297,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method uses three distinct steps to handle the lock:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.22722739,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"get()","depth":27,"bounds":{"left":0.14860372,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":26,"bounds":{"left":0.16555852,"top":0.11412609,"width":0.002493351,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.17004654,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", and","depth":26,"bounds":{"left":0.18700133,"top":0.11412609,"width":0.013131649,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"expire()","depth":27,"bounds":{"left":0.20212767,"top":0.11572227,"width":0.023936171,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.22805852,"top":0.11412609,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If multiple workers process the same","depth":26,"bounds":{"left":0.16456117,"top":0.14285715,"width":0.09275266,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$this->updateTarget","depth":27,"bounds":{"left":0.25930852,"top":0.14445332,"width":0.056848403,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"concurrently, they can both pass the","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"if (! is_null(Redis::get(...)))","depth":27,"bounds":{"left":0.18351063,"top":0.16360734,"width":0.09275266,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"check at the exact same millisecond. Both will then execute","depth":26,"bounds":{"left":0.12450133,"top":0.16201118,"width":0.21575798,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.2122673,"top":0.18276137,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and push duplicate metrics to Datadog.","depth":26,"bounds":{"left":0.22922207,"top":0.1811652,"width":0.09823803,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Fix:","depth":26,"bounds":{"left":0.12450133,"top":0.20989625,"width":0.019281914,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This should be replaced with a single atomic operation using Redis's","depth":26,"bounds":{"left":0.14378324,"top":0.20989625,"width":0.16821809,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"NX","depth":27,"bounds":{"left":0.31399602,"top":0.21149242,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Not eXists) and","depth":26,"bounds":{"left":0.12450133,"top":0.20989625,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"EX","depth":27,"bounds":{"left":0.13696809,"top":0.23064645,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Expire) flags, or Laravel’s built-in atomic cache locks:","depth":26,"bounds":{"left":0.14494681,"top":0.22905028,"width":0.13264628,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis::set($gaugeLockName, true, 'EX', 60, 'NX')","depth":27,"bounds":{"left":0.12649602,"top":0.24980047,"width":0.14361702,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.27210772,"top":0.2482043,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"3. Infinite Loops and Caller Contract","depth":23,"bounds":{"left":0.111369684,"top":0.2980846,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. Infinite Loops and Caller Contract","depth":24,"bounds":{"left":0.111369684,"top":0.29768556,"width":0.10571808,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Return Value Change:","depth":26,"bounds":{"left":0.12450133,"top":0.32482043,"width":0.055352394,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"bounds":{"left":0.18301196,"top":0.3264166,"width":0.06881649,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"previously returned whether it should iterate again based on staleness or chunk size. It now always returns","depth":26,"bounds":{"left":0.12450133,"top":0.32482043,"width":0.2237367,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"bounds":{"left":0.29438165,"top":0.34557062,"width":0.011968086,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(unless the selection is totally empty).","depth":26,"bounds":{"left":0.12450133,"top":0.34397447,"width":0.2130984,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.39185953,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the code calling this method relies on a","depth":26,"bounds":{"left":0.16456117,"top":0.39185953,"width":0.10305851,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"false","depth":27,"bounds":{"left":0.26961437,"top":0.3934557,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"return to pause, sleep, or gracefully terminate, changing this to an unconditional","depth":26,"bounds":{"left":0.12450133,"top":0.39185953,"width":0.22589761,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"bounds":{"left":0.26163563,"top":0.41260973,"width":0.011968086,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"could result in an aggressive \"busy loop\" that spikes CPU usage.","depth":26,"bounds":{"left":0.12450133,"top":0.41101357,"width":0.22290559,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"4. Performance Overhead","depth":23,"bounds":{"left":0.111369684,"top":0.48004788,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4. Performance Overhead","depth":24,"bounds":{"left":0.111369684,"top":0.47964883,"width":0.076130316,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Expensive Garbage Collection:","depth":26,"bounds":{"left":0.12450133,"top":0.5067837,"width":0.078457445,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Calling","depth":26,"bounds":{"left":0.20295878,"top":0.5067837,"width":0.019281914,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles();","depth":27,"bounds":{"left":0.22423537,"top":0.5083799,"width":0.059840426,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"on","depth":26,"bounds":{"left":0.28607047,"top":0.5067837,"width":0.00880984,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"every single chunk","depth":26,"bounds":{"left":0.29488033,"top":0.5067837,"width":0.045545213,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.","depth":26,"bounds":{"left":0.12450133,"top":0.5067837,"width":0.23420878,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Synchronous Metric Dispatch:","depth":26,"bounds":{"left":0.12450133,"top":0.5738228,"width":0.07712766,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the","depth":26,"bounds":{"left":0.20162898,"top":0.5738228,"width":0.015292553,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Datadog::increment","depth":27,"bounds":{"left":0.21891622,"top":0.575419,"width":0.053856384,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"or","depth":26,"bounds":{"left":0.27476728,"top":0.5738228,"width":0.0078125,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Datadog::gauge","depth":27,"bounds":{"left":0.28457448,"top":0.575419,"width":0.041888297,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.","depth":26,"bounds":{"left":0.12450133,"top":0.5738228,"width":0.23420878,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"5. Lack of Explicit Error Handling","depth":23,"bounds":{"left":0.111369684,"top":0.66201115,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5. Lack of Explicit Error Handling","depth":24,"bounds":{"left":0.111369684,"top":0.66161215,"width":0.094913565,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No","depth":26,"bounds":{"left":0.12450133,"top":0.688747,"width":0.008477394,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"try/catch","depth":27,"bounds":{"left":0.1349734,"top":0.6903432,"width":0.026928192,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Blocks:","depth":26,"bounds":{"left":0.16389628,"top":0.688747,"width":0.019614361,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The script executes complex network operations (","depth":26,"bounds":{"left":0.18351063,"top":0.688747,"width":0.12367021,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"loadDocuments","depth":27,"bounds":{"left":0.30917552,"top":0.6903432,"width":0.038896278,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.","depth":26,"bounds":{"left":0.12450133,"top":0.688747,"width":0.22888963,"height":0.09377494},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":22,"bounds":{"left":0.1087101,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":22,"bounds":{"left":0.124667555,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Redo","depth":22,"bounds":{"left":0.140625,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":21,"bounds":{"left":0.15658244,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":22,"bounds":{"left":0.17253989,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":21,"bounds":{"left":0.18849733,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXTextArea","text":"Ask Gemini","depth":21,"bounds":{"left":0.14594415,"top":0.86632085,"width":0.17021276,"height":0.01915403},"on_screen":true,"value":"Ask Gemini","help_text":"","role_description":"text entry area","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Ask Gemini","depth":22,"bounds":{"left":0.14594415,"top":0.8667199,"width":0.030418882,"height":0.018355945},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Upload & tools","depth":20,"bounds":{"left":0.12732713,"top":0.8567438,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Microphone","depth":20,"bounds":{"left":0.32413563,"top":0.86153233,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Send message","depth":20,"bounds":{"left":0.33211437,"top":0.8631285,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Your Jiminny chats aren’t used to improve our models. Gemini is AI. It can make mistakes, so double check it.","depth":17,"bounds":{"left":0.102726065,"top":0.9173983,"width":0.21775267,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Your privacy & Gemini Opens in a new window","depth":17,"bounds":{"left":0.32047874,"top":0.9173983,"width":0.043882977,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Your privacy & Gemini","depth":18,"bounds":{"left":0.32047874,"top":0.9173983,"width":0.043882977,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Opens in a new window","depth":19,"bounds":{"left":0.079288565,"top":0.91660017,"width":0.04720745,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Summarize page","depth":7,"bounds":{"left":0.08494016,"top":0.95730245,"width":0.053523935,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Summarize page","depth":9,"bounds":{"left":0.09059176,"top":0.96249,"width":0.042220745,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Skip to content","depth":6,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to content","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open menu","depth":10,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Homepage (g then d)","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"jiminny","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"jiminny","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"app","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"app","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Search or jump to…","depth":9,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Type","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to search","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Chat with Copilot","depth":10,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Open Copilot…","depth":9,"on_screen":false,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Create new...","depth":9,"on_screen":false,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"All issues(g then i)","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"All pull requests","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"All repositories","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"You have unread notifications(g then n)","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open user navigation menu","depth":9,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Repository navigation","depth":9,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Repository navigation","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Code","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Code","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Pull requests (31)","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pull requests","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"31","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Agents","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Agents","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Actions","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Actions","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Wiki","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Wiki","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Security and quality","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Security and quality","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Insights","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Insights","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Settings","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Settings","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"JY-20676 delete AJ reports related objects #12098 Edit title","depth":13,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"#","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"12098","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Edit title","depth":14,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Checks pending","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Checks pending","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Code","depth":13,"on_screen":false,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Code","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Open","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"LakyLak","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"LakyLak","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"wants to merge 20 commits into","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"master","depth":15,"on_screen":false,"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"master","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"from","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"JY-20676-delete-report-related-objects","depth":16,"on_screen":false,"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20676-delete-report-related-objects","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy head branch name to clipboard","depth":16,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Lines changed: 1698 additions & 1471 deletions","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Conversation (9)","depth":16,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Conversation","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
8863232250382509861
|
-1052796009671526500
|
click
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage.
4. Performance Overhead
4. Performance Overhead
Expensive Garbage Collection:
Calling
gc_collect_cycles();
on
every single chunk
can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.
Synchronous Metric Dispatch:
If the
Datadog::increment
or
Datadog::gauge
methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.
5. Lack of Explicit Error Handling
5. Lack of Explicit Error Handling
No
try/catch
Blocks:
The script executes complex network operations (
loadDocuments
, Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.
Good response
Bad response
Redo
Share & export
Copy
Show more options
Ask Gemini
Ask Gemini
Upload & tools
Microphone
Send message
Your Jiminny chats aren’t used to improve our models. Gemini is AI. It can make mistakes, so double check it.
Your privacy & Gemini Opens in a new window
Your privacy & Gemini
Opens in a new window
Summarize page
Summarize page
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
app
app
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
All issues(g then i)
All pull requests
All repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Pull requests (31)
Pull requests
(
31
)
Agents
Agents
Actions
Actions
Wiki
Wiki
Security and quality
Security and quality
Insights
Insights
Settings
Settings
JY-20676 delete AJ reports related objects #12098 Edit title
JY-20676 delete AJ reports related objects
#
12098
Edit title
Checks pending
Checks pending
Code
Code
Open
LakyLak
LakyLak
wants to merge 20 commits into
master
master
from
JY-20676-delete-report-related-objects
JY-20676-delete-report-related-objects
Copy head branch name to clipboard
Lines changed: 1698 additions & 1471 deletions
Conversation (9)
Conversation
(...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
64662
|
2265
|
45
|
2026-05-20T14:10:47.644560+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779286247644_m2.jpg...
|
Firefox
|
JY-20676 delete AJ reports related objects by Laky JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app — Work...
|
1
|
github.com/jiminny/app/pull/12098
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage.
4. Performance Overhead
4. Performance Overhead
Expensive Garbage Collection:
Calling
gc_collect_cycles();
on
every single chunk
can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.
Synchronous Metric Dispatch:
If the
Datadog::increment
or
Datadog::gauge
methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.
5. Lack of Explicit Error Handling
5. Lack of Explicit Error Handling
No
try/catch
Blocks:
The script executes complex network operations (
loadDocuments
, Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.
Good response
Bad response
Redo
Share & export
Copy
Show more options
Ask Gemini
Ask Gemini
Upload & tools
Microphone
Send message
Your Jiminny chats aren’t used to improve our models. Gemini is AI. It can make mistakes, so double check it.
Your privacy & Gemini Opens in a new window
Your privacy & Gemini
Opens in a new window
Summarize page
Summarize page
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
app
app
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
All issues(g then i)
All pull requests
All repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Pull requests (31)
Pull requests...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.10721409,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.15791224,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.1245012,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.1796875,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.12699468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":4,"bounds":{"left":0.0,"top":0.21548285,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":5,"bounds":{"left":0.013297873,"top":0.22665602,"width":0.10139628,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"bounds":{"left":0.0,"top":0.2482043,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.25937748,"width":0.18068483,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.28092578,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.29209897,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.31364724,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.32482043,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.3463687,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.3575419,"width":0.15824468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.38068634,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"bounds":{"left":0.35854387,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"bounds":{"left":0.37051198,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Main menu","depth":13,"bounds":{"left":0.08228058,"top":0.09736632,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New chat","depth":12,"bounds":{"left":0.09823803,"top":0.10215483,"width":0.028091755,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":15,"bounds":{"left":0.1008976,"top":0.10654429,"width":0.02144282,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Chat","depth":13,"bounds":{"left":0.35854387,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":13,"bounds":{"left":0.37051198,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"bounds":{"left":0.079288565,"top":0.14445332,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"bounds":{"left":0.079288565,"top":0.14684756,"width":0.1200133,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"You said I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a concise summary of the changes made in the code selection:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Refactoring and Dependencies","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Refactoring and Dependencies","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Replaced Interface with Implementation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusInterface","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusManager","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in the imports and constructor dependency injection.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Redis Support:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Imported","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Illuminate\\Support\\Facades\\Redis","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to handle a newly introduced memory-logging lock mechanism.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Traits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WorkerIdTrait","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", along with the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"worker","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"field tracking in the final logs.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Loop and Worker Lifecycle Adjustments","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Loop and Worker Lifecycle Adjustments","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Worker Auto-Restart:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Deleted the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property, the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"constant, and the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"shouldIterateAgain()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method. The script no longer tracks execution iterations or triggers a self-managed worker restart.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Simplified Chunk Method Return:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Modified","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to always return","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Priority Handling for Throttled Entities","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Priority Handling for Throttled Entities","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Stale Record Elevation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Updated the logic in","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"rescheduleThrottledEntities()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to check if a throttled entity group is classified as \"stale\". If it is, the entities are rescheduled using","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"QueuePriorityEnum::NORMAL","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"; otherwise, they keep their respective queue priority.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Metrics, Logging, and Environment Handling","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Metrics, Logging, and Environment Handling","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Environment Tracking:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Introduced a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$environment","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property (defaulting to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'local'","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") and a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"setEnvironment()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method to dynamically assign it.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Memory Monitoring via Datadog Gauge:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method that submits peak memory allocation to Datadog as a gauge metric (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"jiminny.reindex-memory-usage","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis Lock for Metrics Throttling:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Implemented a Redis-backed lock (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"%s-process-manager-lock","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") with a 60-second expiration inside","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to prevent over-reporting metrics.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Log Payload Update:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added total processed items (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$updatedDocuments + $deletedDocuments","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") to the final information log.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":23,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said are there any issues or potential bottlenecks","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"are there any issues or potential bottlenecks","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a breakdown of the potential issues to look out for:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"1. Memory Leak Risk (OOM Crashes)","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. Memory Leak Risk (OOM Crashes)","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Restart Logic:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The most significant change is the removal of the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Dependency Leaks:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If any underlying service (like the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ElasticSearchClient","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", ORM, or Datadog helper) retains references to processed entities,","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"bounds":{"left":0.26811835,"top":0.0,"width":0.056848403,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23138298,"height":0.05546688},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Hardcoded Limits:","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.047041222,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The code still includes","depth":26,"bounds":{"left":0.17154256,"top":0.0,"width":0.05651596,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ini_set('memory_limit', '512M');","depth":27,"bounds":{"left":0.23005319,"top":0.0,"width":0.095744684,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23055187,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"2. Race Condition in Redis Lock","depth":23,"bounds":{"left":0.111369684,"top":0.06823623,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. Race Condition in Redis Lock","depth":24,"bounds":{"left":0.111369684,"top":0.06783719,"width":0.09125665,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Non-Atomic Operations:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.062333778,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The","depth":26,"bounds":{"left":0.18683511,"top":0.09497207,"width":0.011635638,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage","depth":27,"bounds":{"left":0.20046543,"top":0.096568234,"width":0.041888297,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method uses three distinct steps to handle the lock:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.22722739,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"get()","depth":27,"bounds":{"left":0.14860372,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":26,"bounds":{"left":0.16555852,"top":0.11412609,"width":0.002493351,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.17004654,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", and","depth":26,"bounds":{"left":0.18700133,"top":0.11412609,"width":0.013131649,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"expire()","depth":27,"bounds":{"left":0.20212767,"top":0.11572227,"width":0.023936171,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.22805852,"top":0.11412609,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If multiple workers process the same","depth":26,"bounds":{"left":0.16456117,"top":0.14285715,"width":0.09275266,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$this->updateTarget","depth":27,"bounds":{"left":0.25930852,"top":0.14445332,"width":0.056848403,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"concurrently, they can both pass the","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"if (! is_null(Redis::get(...)))","depth":27,"bounds":{"left":0.18351063,"top":0.16360734,"width":0.09275266,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"check at the exact same millisecond. Both will then execute","depth":26,"bounds":{"left":0.12450133,"top":0.16201118,"width":0.21575798,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.2122673,"top":0.18276137,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and push duplicate metrics to Datadog.","depth":26,"bounds":{"left":0.22922207,"top":0.1811652,"width":0.09823803,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Fix:","depth":26,"bounds":{"left":0.12450133,"top":0.20989625,"width":0.019281914,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This should be replaced with a single atomic operation using Redis's","depth":26,"bounds":{"left":0.14378324,"top":0.20989625,"width":0.16821809,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"NX","depth":27,"bounds":{"left":0.31399602,"top":0.21149242,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Not eXists) and","depth":26,"bounds":{"left":0.12450133,"top":0.20989625,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"EX","depth":27,"bounds":{"left":0.13696809,"top":0.23064645,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Expire) flags, or Laravel’s built-in atomic cache locks:","depth":26,"bounds":{"left":0.14494681,"top":0.22905028,"width":0.13264628,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis::set($gaugeLockName, true, 'EX', 60, 'NX')","depth":27,"bounds":{"left":0.12649602,"top":0.24980047,"width":0.14361702,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.27210772,"top":0.2482043,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"3. Infinite Loops and Caller Contract","depth":23,"bounds":{"left":0.111369684,"top":0.2980846,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. Infinite Loops and Caller Contract","depth":24,"bounds":{"left":0.111369684,"top":0.29768556,"width":0.10571808,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Return Value Change:","depth":26,"bounds":{"left":0.12450133,"top":0.32482043,"width":0.055352394,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"bounds":{"left":0.18301196,"top":0.3264166,"width":0.06881649,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"previously returned whether it should iterate again based on staleness or chunk size. It now always returns","depth":26,"bounds":{"left":0.12450133,"top":0.32482043,"width":0.2237367,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"bounds":{"left":0.29438165,"top":0.34557062,"width":0.011968086,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(unless the selection is totally empty).","depth":26,"bounds":{"left":0.12450133,"top":0.34397447,"width":0.2130984,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.39185953,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the code calling this method relies on a","depth":26,"bounds":{"left":0.16456117,"top":0.39185953,"width":0.10305851,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"false","depth":27,"bounds":{"left":0.26961437,"top":0.3934557,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"return to pause, sleep, or gracefully terminate, changing this to an unconditional","depth":26,"bounds":{"left":0.12450133,"top":0.39185953,"width":0.22589761,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"bounds":{"left":0.26163563,"top":0.41260973,"width":0.011968086,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"could result in an aggressive \"busy loop\" that spikes CPU usage.","depth":26,"bounds":{"left":0.12450133,"top":0.41101357,"width":0.22290559,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"4. Performance Overhead","depth":23,"bounds":{"left":0.111369684,"top":0.48004788,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4. Performance Overhead","depth":24,"bounds":{"left":0.111369684,"top":0.47964883,"width":0.076130316,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Expensive Garbage Collection:","depth":26,"bounds":{"left":0.12450133,"top":0.5067837,"width":0.078457445,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Calling","depth":26,"bounds":{"left":0.20295878,"top":0.5067837,"width":0.019281914,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles();","depth":27,"bounds":{"left":0.22423537,"top":0.5083799,"width":0.059840426,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"on","depth":26,"bounds":{"left":0.28607047,"top":0.5067837,"width":0.00880984,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"every single chunk","depth":26,"bounds":{"left":0.29488033,"top":0.5067837,"width":0.045545213,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.","depth":26,"bounds":{"left":0.12450133,"top":0.5067837,"width":0.23420878,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Synchronous Metric Dispatch:","depth":26,"bounds":{"left":0.12450133,"top":0.5738228,"width":0.07712766,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the","depth":26,"bounds":{"left":0.20162898,"top":0.5738228,"width":0.015292553,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Datadog::increment","depth":27,"bounds":{"left":0.21891622,"top":0.575419,"width":0.053856384,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"or","depth":26,"bounds":{"left":0.27476728,"top":0.5738228,"width":0.0078125,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Datadog::gauge","depth":27,"bounds":{"left":0.28457448,"top":0.575419,"width":0.041888297,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.","depth":26,"bounds":{"left":0.12450133,"top":0.5738228,"width":0.23420878,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"5. Lack of Explicit Error Handling","depth":23,"bounds":{"left":0.111369684,"top":0.66201115,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5. Lack of Explicit Error Handling","depth":24,"bounds":{"left":0.111369684,"top":0.66161215,"width":0.094913565,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No","depth":26,"bounds":{"left":0.12450133,"top":0.688747,"width":0.008477394,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"try/catch","depth":27,"bounds":{"left":0.1349734,"top":0.6903432,"width":0.026928192,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Blocks:","depth":26,"bounds":{"left":0.16389628,"top":0.688747,"width":0.019614361,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The script executes complex network operations (","depth":26,"bounds":{"left":0.18351063,"top":0.688747,"width":0.12367021,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"loadDocuments","depth":27,"bounds":{"left":0.30917552,"top":0.6903432,"width":0.038896278,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.","depth":26,"bounds":{"left":0.12450133,"top":0.688747,"width":0.22888963,"height":0.09377494},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":22,"bounds":{"left":0.1087101,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":22,"bounds":{"left":0.124667555,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Redo","depth":22,"bounds":{"left":0.140625,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":21,"bounds":{"left":0.15658244,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":22,"bounds":{"left":0.17253989,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":21,"bounds":{"left":0.18849733,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXTextArea","text":"Ask Gemini","depth":21,"bounds":{"left":0.14594415,"top":0.86632085,"width":0.17021276,"height":0.01915403},"on_screen":true,"value":"Ask Gemini","help_text":"","role_description":"text entry area","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Ask Gemini","depth":22,"bounds":{"left":0.14594415,"top":0.8667199,"width":0.030418882,"height":0.018355945},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Upload & tools","depth":20,"bounds":{"left":0.12732713,"top":0.8567438,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Microphone","depth":20,"bounds":{"left":0.32413563,"top":0.86153233,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Send message","depth":20,"bounds":{"left":0.33211437,"top":0.8631285,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Your Jiminny chats aren’t used to improve our models. Gemini is AI. It can make mistakes, so double check it.","depth":17,"bounds":{"left":0.102726065,"top":0.9173983,"width":0.21775267,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Your privacy & Gemini Opens in a new window","depth":17,"bounds":{"left":0.32047874,"top":0.9173983,"width":0.043882977,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Your privacy & Gemini","depth":18,"bounds":{"left":0.32047874,"top":0.9173983,"width":0.043882977,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Opens in a new window","depth":19,"bounds":{"left":0.079288565,"top":0.91660017,"width":0.04720745,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Summarize page","depth":7,"bounds":{"left":0.08494016,"top":0.95730245,"width":0.053523935,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Summarize page","depth":9,"bounds":{"left":0.09059176,"top":0.96249,"width":0.042220745,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Skip to content","depth":6,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to content","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open menu","depth":10,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Homepage (g then d)","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"jiminny","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"jiminny","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"app","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"app","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Search or jump to…","depth":9,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Type","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to search","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Chat with Copilot","depth":10,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Open Copilot…","depth":9,"on_screen":false,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Create new...","depth":9,"on_screen":false,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"All issues(g then i)","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"All pull requests","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"All repositories","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"You have unread notifications(g then n)","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open user navigation menu","depth":9,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Repository navigation","depth":9,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Repository navigation","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Code","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Code","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Pull requests (31)","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pull requests","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
5720481319462978766
|
-1052796009671460964
|
visual_change
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage.
4. Performance Overhead
4. Performance Overhead
Expensive Garbage Collection:
Calling
gc_collect_cycles();
on
every single chunk
can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.
Synchronous Metric Dispatch:
If the
Datadog::increment
or
Datadog::gauge
methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.
5. Lack of Explicit Error Handling
5. Lack of Explicit Error Handling
No
try/catch
Blocks:
The script executes complex network operations (
loadDocuments
, Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.
Good response
Bad response
Redo
Share & export
Copy
Show more options
Ask Gemini
Ask Gemini
Upload & tools
Microphone
Send message
Your Jiminny chats aren’t used to improve our models. Gemini is AI. It can make mistakes, so double check it.
Your privacy & Gemini Opens in a new window
Your privacy & Gemini
Opens in a new window
Summarize page
Summarize page
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
app
app
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
All issues(g then i)
All pull requests
All repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Pull requests (31)
Pull requests...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
64663
|
2265
|
46
|
2026-05-20T14:10:52.471636+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779286252471_m2.jpg...
|
Firefox
|
JY-20676 delete AJ reports related objects by Laky JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app — Work...
|
1
|
github.com/jiminny/app/pull/12098
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage.
4. Performance Overhead
4. Performance Overhead
Expensive Garbage Collection:
Calling
gc_collect_cycles();
on
every single chunk
can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.
Synchronous Metric Dispatch:
If the
Datadog::increment
or
Datadog::gauge
methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.
5. Lack of Explicit Error Handling
5. Lack of Explicit Error Handling
No
try/catch
Blocks:
The script executes complex network operations (
loadDocuments
, Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.
Good response
Bad response
Redo
Share & export
Copy
Show more options
Ask Gemini
Ask Gemini
Upload & tools
Microphone
Send message
Your Jiminny chats aren’t used to improve our models. Gemini is AI. It can make mistakes, so double check it.
Your privacy & Gemini Opens in a new window
Your privacy & Gemini
Opens in a new window
Summarize page
Summarize page
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
app
app
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
All issues(g then i)
All pull requests
All repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Pull requests (31)...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.10721409,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.15791224,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.1245012,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.1796875,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.12699468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":4,"bounds":{"left":0.0,"top":0.21548285,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":5,"bounds":{"left":0.013297873,"top":0.22665602,"width":0.10139628,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"bounds":{"left":0.0,"top":0.2482043,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.25937748,"width":0.18068483,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.28092578,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.29209897,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.31364724,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.32482043,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.3463687,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.3575419,"width":0.15824468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.38068634,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"bounds":{"left":0.35854387,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"bounds":{"left":0.37051198,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Main menu","depth":13,"bounds":{"left":0.08228058,"top":0.09736632,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New chat","depth":12,"bounds":{"left":0.09823803,"top":0.10215483,"width":0.028091755,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":15,"bounds":{"left":0.1008976,"top":0.10654429,"width":0.02144282,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Chat","depth":13,"bounds":{"left":0.35854387,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":13,"bounds":{"left":0.37051198,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"bounds":{"left":0.079288565,"top":0.14445332,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"bounds":{"left":0.079288565,"top":0.14684756,"width":0.1200133,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"You said I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a concise summary of the changes made in the code selection:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Refactoring and Dependencies","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Refactoring and Dependencies","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Replaced Interface with Implementation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusInterface","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusManager","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in the imports and constructor dependency injection.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Redis Support:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Imported","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Illuminate\\Support\\Facades\\Redis","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to handle a newly introduced memory-logging lock mechanism.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Traits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WorkerIdTrait","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", along with the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"worker","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"field tracking in the final logs.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Loop and Worker Lifecycle Adjustments","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Loop and Worker Lifecycle Adjustments","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Worker Auto-Restart:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Deleted the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property, the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"constant, and the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"shouldIterateAgain()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method. The script no longer tracks execution iterations or triggers a self-managed worker restart.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Simplified Chunk Method Return:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Modified","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to always return","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Priority Handling for Throttled Entities","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Priority Handling for Throttled Entities","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Stale Record Elevation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Updated the logic in","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"rescheduleThrottledEntities()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to check if a throttled entity group is classified as \"stale\". If it is, the entities are rescheduled using","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"QueuePriorityEnum::NORMAL","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"; otherwise, they keep their respective queue priority.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Metrics, Logging, and Environment Handling","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Metrics, Logging, and Environment Handling","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Environment Tracking:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Introduced a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$environment","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property (defaulting to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'local'","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") and a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"setEnvironment()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method to dynamically assign it.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Memory Monitoring via Datadog Gauge:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method that submits peak memory allocation to Datadog as a gauge metric (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"jiminny.reindex-memory-usage","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis Lock for Metrics Throttling:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Implemented a Redis-backed lock (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"%s-process-manager-lock","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") with a 60-second expiration inside","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to prevent over-reporting metrics.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Log Payload Update:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added total processed items (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$updatedDocuments + $deletedDocuments","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") to the final information log.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":23,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said are there any issues or potential bottlenecks","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"are there any issues or potential bottlenecks","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a breakdown of the potential issues to look out for:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"1. Memory Leak Risk (OOM Crashes)","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. Memory Leak Risk (OOM Crashes)","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Restart Logic:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The most significant change is the removal of the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Dependency Leaks:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If any underlying service (like the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ElasticSearchClient","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", ORM, or Datadog helper) retains references to processed entities,","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"bounds":{"left":0.26811835,"top":0.0,"width":0.056848403,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23138298,"height":0.05546688},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Hardcoded Limits:","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.047041222,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The code still includes","depth":26,"bounds":{"left":0.17154256,"top":0.0,"width":0.05651596,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ini_set('memory_limit', '512M');","depth":27,"bounds":{"left":0.23005319,"top":0.0,"width":0.095744684,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23055187,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"2. Race Condition in Redis Lock","depth":23,"bounds":{"left":0.111369684,"top":0.06823623,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. Race Condition in Redis Lock","depth":24,"bounds":{"left":0.111369684,"top":0.06783719,"width":0.09125665,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Non-Atomic Operations:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.062333778,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The","depth":26,"bounds":{"left":0.18683511,"top":0.09497207,"width":0.011635638,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage","depth":27,"bounds":{"left":0.20046543,"top":0.096568234,"width":0.041888297,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method uses three distinct steps to handle the lock:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.22722739,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"get()","depth":27,"bounds":{"left":0.14860372,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":26,"bounds":{"left":0.16555852,"top":0.11412609,"width":0.002493351,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.17004654,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", and","depth":26,"bounds":{"left":0.18700133,"top":0.11412609,"width":0.013131649,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"expire()","depth":27,"bounds":{"left":0.20212767,"top":0.11572227,"width":0.023936171,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.22805852,"top":0.11412609,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If multiple workers process the same","depth":26,"bounds":{"left":0.16456117,"top":0.14285715,"width":0.09275266,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$this->updateTarget","depth":27,"bounds":{"left":0.25930852,"top":0.14445332,"width":0.056848403,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"concurrently, they can both pass the","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"if (! is_null(Redis::get(...)))","depth":27,"bounds":{"left":0.18351063,"top":0.16360734,"width":0.09275266,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"check at the exact same millisecond. Both will then execute","depth":26,"bounds":{"left":0.12450133,"top":0.16201118,"width":0.21575798,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.2122673,"top":0.18276137,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and push duplicate metrics to Datadog.","depth":26,"bounds":{"left":0.22922207,"top":0.1811652,"width":0.09823803,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Fix:","depth":26,"bounds":{"left":0.12450133,"top":0.20989625,"width":0.019281914,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This should be replaced with a single atomic operation using Redis's","depth":26,"bounds":{"left":0.14378324,"top":0.20989625,"width":0.16821809,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"NX","depth":27,"bounds":{"left":0.31399602,"top":0.21149242,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Not eXists) and","depth":26,"bounds":{"left":0.12450133,"top":0.20989625,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"EX","depth":27,"bounds":{"left":0.13696809,"top":0.23064645,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Expire) flags, or Laravel’s built-in atomic cache locks:","depth":26,"bounds":{"left":0.14494681,"top":0.22905028,"width":0.13264628,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis::set($gaugeLockName, true, 'EX', 60, 'NX')","depth":27,"bounds":{"left":0.12649602,"top":0.24980047,"width":0.14361702,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.27210772,"top":0.2482043,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"3. Infinite Loops and Caller Contract","depth":23,"bounds":{"left":0.111369684,"top":0.2980846,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. Infinite Loops and Caller Contract","depth":24,"bounds":{"left":0.111369684,"top":0.29768556,"width":0.10571808,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Return Value Change:","depth":26,"bounds":{"left":0.12450133,"top":0.32482043,"width":0.055352394,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"bounds":{"left":0.18301196,"top":0.3264166,"width":0.06881649,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"previously returned whether it should iterate again based on staleness or chunk size. It now always returns","depth":26,"bounds":{"left":0.12450133,"top":0.32482043,"width":0.2237367,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"bounds":{"left":0.29438165,"top":0.34557062,"width":0.011968086,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(unless the selection is totally empty).","depth":26,"bounds":{"left":0.12450133,"top":0.34397447,"width":0.2130984,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.39185953,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the code calling this method relies on a","depth":26,"bounds":{"left":0.16456117,"top":0.39185953,"width":0.10305851,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"false","depth":27,"bounds":{"left":0.26961437,"top":0.3934557,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"return to pause, sleep, or gracefully terminate, changing this to an unconditional","depth":26,"bounds":{"left":0.12450133,"top":0.39185953,"width":0.22589761,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"bounds":{"left":0.26163563,"top":0.41260973,"width":0.011968086,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"could result in an aggressive \"busy loop\" that spikes CPU usage.","depth":26,"bounds":{"left":0.12450133,"top":0.41101357,"width":0.22290559,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"4. Performance Overhead","depth":23,"bounds":{"left":0.111369684,"top":0.48004788,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4. Performance Overhead","depth":24,"bounds":{"left":0.111369684,"top":0.47964883,"width":0.076130316,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Expensive Garbage Collection:","depth":26,"bounds":{"left":0.12450133,"top":0.5067837,"width":0.078457445,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Calling","depth":26,"bounds":{"left":0.20295878,"top":0.5067837,"width":0.019281914,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles();","depth":27,"bounds":{"left":0.22423537,"top":0.5083799,"width":0.059840426,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"on","depth":26,"bounds":{"left":0.28607047,"top":0.5067837,"width":0.00880984,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"every single chunk","depth":26,"bounds":{"left":0.29488033,"top":0.5067837,"width":0.045545213,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.","depth":26,"bounds":{"left":0.12450133,"top":0.5067837,"width":0.23420878,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Synchronous Metric Dispatch:","depth":26,"bounds":{"left":0.12450133,"top":0.5738228,"width":0.07712766,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the","depth":26,"bounds":{"left":0.20162898,"top":0.5738228,"width":0.015292553,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Datadog::increment","depth":27,"bounds":{"left":0.21891622,"top":0.575419,"width":0.053856384,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"or","depth":26,"bounds":{"left":0.27476728,"top":0.5738228,"width":0.0078125,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Datadog::gauge","depth":27,"bounds":{"left":0.28457448,"top":0.575419,"width":0.041888297,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.","depth":26,"bounds":{"left":0.12450133,"top":0.5738228,"width":0.23420878,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"5. Lack of Explicit Error Handling","depth":23,"bounds":{"left":0.111369684,"top":0.66201115,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5. Lack of Explicit Error Handling","depth":24,"bounds":{"left":0.111369684,"top":0.66161215,"width":0.094913565,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No","depth":26,"bounds":{"left":0.12450133,"top":0.688747,"width":0.008477394,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"try/catch","depth":27,"bounds":{"left":0.1349734,"top":0.6903432,"width":0.026928192,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Blocks:","depth":26,"bounds":{"left":0.16389628,"top":0.688747,"width":0.019614361,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The script executes complex network operations (","depth":26,"bounds":{"left":0.18351063,"top":0.688747,"width":0.12367021,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"loadDocuments","depth":27,"bounds":{"left":0.30917552,"top":0.6903432,"width":0.038896278,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.","depth":26,"bounds":{"left":0.12450133,"top":0.688747,"width":0.22888963,"height":0.09377494},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":22,"bounds":{"left":0.1087101,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":22,"bounds":{"left":0.124667555,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Redo","depth":22,"bounds":{"left":0.140625,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":21,"bounds":{"left":0.15658244,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":22,"bounds":{"left":0.17253989,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":21,"bounds":{"left":0.18849733,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXTextArea","text":"Ask Gemini","depth":21,"bounds":{"left":0.14594415,"top":0.86632085,"width":0.17021276,"height":0.01915403},"on_screen":true,"value":"Ask Gemini","help_text":"","role_description":"text entry area","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Ask Gemini","depth":22,"bounds":{"left":0.14594415,"top":0.8667199,"width":0.030418882,"height":0.018355945},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Upload & tools","depth":20,"bounds":{"left":0.12732713,"top":0.8567438,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Microphone","depth":20,"bounds":{"left":0.32413563,"top":0.86153233,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Send message","depth":20,"bounds":{"left":0.33211437,"top":0.8631285,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Your Jiminny chats aren’t used to improve our models. Gemini is AI. It can make mistakes, so double check it.","depth":17,"bounds":{"left":0.102726065,"top":0.9173983,"width":0.21775267,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Your privacy & Gemini Opens in a new window","depth":17,"bounds":{"left":0.32047874,"top":0.9173983,"width":0.043882977,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Your privacy & Gemini","depth":18,"bounds":{"left":0.32047874,"top":0.9173983,"width":0.043882977,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Opens in a new window","depth":19,"bounds":{"left":0.079288565,"top":0.91660017,"width":0.04720745,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Summarize page","depth":7,"bounds":{"left":0.08494016,"top":0.95730245,"width":0.053523935,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Summarize page","depth":9,"bounds":{"left":0.09059176,"top":0.96249,"width":0.042220745,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Skip to content","depth":6,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to content","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open menu","depth":10,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Homepage (g then d)","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"jiminny","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"jiminny","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"app","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"app","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Search or jump to…","depth":9,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Type","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to search","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Chat with Copilot","depth":10,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Open Copilot…","depth":9,"on_screen":false,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Create new...","depth":9,"on_screen":false,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"All issues(g then i)","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"All pull requests","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"All repositories","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"You have unread notifications(g then n)","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open user navigation menu","depth":9,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Repository navigation","depth":9,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Repository navigation","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Code","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Code","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Pull requests (31)","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
-6743849867383361544
|
-1052796009671460964
|
click
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage.
4. Performance Overhead
4. Performance Overhead
Expensive Garbage Collection:
Calling
gc_collect_cycles();
on
every single chunk
can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.
Synchronous Metric Dispatch:
If the
Datadog::increment
or
Datadog::gauge
methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.
5. Lack of Explicit Error Handling
5. Lack of Explicit Error Handling
No
try/catch
Blocks:
The script executes complex network operations (
loadDocuments
, Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.
Good response
Bad response
Redo
Share & export
Copy
Show more options
Ask Gemini
Ask Gemini
Upload & tools
Microphone
Send message
Your Jiminny chats aren’t used to improve our models. Gemini is AI. It can make mistakes, so double check it.
Your privacy & Gemini Opens in a new window
Your privacy & Gemini
Opens in a new window
Summarize page
Summarize page
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
app
app
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
All issues(g then i)
All pull requests
All repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Pull requests (31)...
|
64662
|
NULL
|
NULL
|
NULL
|
|
64664
|
2265
|
47
|
2026-05-20T14:10:57.531698+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779286257531_m2.jpg...
|
Firefox
|
JY-20676 delete AJ reports related objects by Laky JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app — Work...
|
1
|
github.com/jiminny/app/pull/12098
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage.
4. Performance Overhead
4. Performance Overhead
Expensive Garbage Collection:
Calling
gc_collect_cycles();
on
every single chunk
can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.
Synchronous Metric Dispatch:
If the
Datadog::increment
or
Datadog::gauge
methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.
5. Lack of Explicit Error Handling
5. Lack of Explicit Error Handling
No
try/catch
Blocks:
The script executes complex network operations (
loadDocuments
, Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.
Good response
Bad response
Redo
Share & export
Copy
Show more options
Ask Gemini
Ask Gemini
Upload & tools
Microphone
Send message
Your Jiminny chats aren’t used to improve our models. Gemini is AI. It can make mistakes, so double check it.
Your privacy & Gemini Opens in a new window
Your privacy & Gemini
Opens in a new window
Summarize page
Summarize page
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
app
app
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
All issues(g then i)
All pull requests
All repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Pull requests (31)
Pull requests
(
31
)
Agents
Agents
Actions
Actions
Wiki
Wiki
Security and quality
Security and quality
Insights
Insights
Settings
Settings
JY-20676 delete AJ reports related objects #12098 Edit title
JY-20676 delete AJ reports related objects
#
12098
Edit title
Checks pending
Checks pending
Code
Code
Open
LakyLak
LakyLak
wants to merge 20 commits into
master
master
from
JY-20676-delete-report-related-objects
JY-20676-delete-report-related-objects
Copy head branch name to clipboard
Lines changed: 1698 additions & 1471 deletions
Conversation (9)
Conversation
(
9
)
Commits (20)
Commits
(
20...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.10721409,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.15791224,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.1245012,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.1796875,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.12699468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":4,"bounds":{"left":0.0,"top":0.21548285,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":5,"bounds":{"left":0.013297873,"top":0.22665602,"width":0.10139628,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"bounds":{"left":0.0,"top":0.2482043,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.25937748,"width":0.18068483,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.28092578,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.29209897,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.31364724,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.32482043,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.3463687,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.3575419,"width":0.15824468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.38068634,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"bounds":{"left":0.35854387,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"bounds":{"left":0.37051198,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Main menu","depth":13,"bounds":{"left":0.08228058,"top":0.09736632,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New chat","depth":12,"bounds":{"left":0.09823803,"top":0.10215483,"width":0.028091755,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":15,"bounds":{"left":0.1008976,"top":0.10654429,"width":0.02144282,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Chat","depth":13,"bounds":{"left":0.35854387,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":13,"bounds":{"left":0.37051198,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"bounds":{"left":0.079288565,"top":0.14445332,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"bounds":{"left":0.079288565,"top":0.14684756,"width":0.1200133,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"You said I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a concise summary of the changes made in the code selection:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Refactoring and Dependencies","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Refactoring and Dependencies","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Replaced Interface with Implementation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusInterface","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusManager","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in the imports and constructor dependency injection.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Redis Support:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Imported","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Illuminate\\Support\\Facades\\Redis","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to handle a newly introduced memory-logging lock mechanism.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Traits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WorkerIdTrait","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", along with the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"worker","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"field tracking in the final logs.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Loop and Worker Lifecycle Adjustments","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Loop and Worker Lifecycle Adjustments","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Worker Auto-Restart:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Deleted the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property, the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"constant, and the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"shouldIterateAgain()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method. The script no longer tracks execution iterations or triggers a self-managed worker restart.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Simplified Chunk Method Return:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Modified","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to always return","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Priority Handling for Throttled Entities","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Priority Handling for Throttled Entities","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Stale Record Elevation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Updated the logic in","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"rescheduleThrottledEntities()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to check if a throttled entity group is classified as \"stale\". If it is, the entities are rescheduled using","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"QueuePriorityEnum::NORMAL","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"; otherwise, they keep their respective queue priority.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Metrics, Logging, and Environment Handling","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Metrics, Logging, and Environment Handling","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Environment Tracking:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Introduced a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$environment","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property (defaulting to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'local'","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") and a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"setEnvironment()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method to dynamically assign it.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Memory Monitoring via Datadog Gauge:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method that submits peak memory allocation to Datadog as a gauge metric (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"jiminny.reindex-memory-usage","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis Lock for Metrics Throttling:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Implemented a Redis-backed lock (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"%s-process-manager-lock","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") with a 60-second expiration inside","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to prevent over-reporting metrics.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Log Payload Update:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added total processed items (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$updatedDocuments + $deletedDocuments","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") to the final information log.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":23,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said are there any issues or potential bottlenecks","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"are there any issues or potential bottlenecks","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a breakdown of the potential issues to look out for:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"1. Memory Leak Risk (OOM Crashes)","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. Memory Leak Risk (OOM Crashes)","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Restart Logic:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The most significant change is the removal of the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Dependency Leaks:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If any underlying service (like the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ElasticSearchClient","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", ORM, or Datadog helper) retains references to processed entities,","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"bounds":{"left":0.26811835,"top":0.0,"width":0.056848403,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23138298,"height":0.05546688},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Hardcoded Limits:","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.047041222,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The code still includes","depth":26,"bounds":{"left":0.17154256,"top":0.0,"width":0.05651596,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ini_set('memory_limit', '512M');","depth":27,"bounds":{"left":0.23005319,"top":0.0,"width":0.095744684,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23055187,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"2. Race Condition in Redis Lock","depth":23,"bounds":{"left":0.111369684,"top":0.06823623,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. Race Condition in Redis Lock","depth":24,"bounds":{"left":0.111369684,"top":0.06783719,"width":0.09125665,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Non-Atomic Operations:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.062333778,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The","depth":26,"bounds":{"left":0.18683511,"top":0.09497207,"width":0.011635638,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage","depth":27,"bounds":{"left":0.20046543,"top":0.096568234,"width":0.041888297,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method uses three distinct steps to handle the lock:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.22722739,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"get()","depth":27,"bounds":{"left":0.14860372,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":26,"bounds":{"left":0.16555852,"top":0.11412609,"width":0.002493351,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.17004654,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", and","depth":26,"bounds":{"left":0.18700133,"top":0.11412609,"width":0.013131649,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"expire()","depth":27,"bounds":{"left":0.20212767,"top":0.11572227,"width":0.023936171,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.22805852,"top":0.11412609,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If multiple workers process the same","depth":26,"bounds":{"left":0.16456117,"top":0.14285715,"width":0.09275266,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$this->updateTarget","depth":27,"bounds":{"left":0.25930852,"top":0.14445332,"width":0.056848403,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"concurrently, they can both pass the","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"if (! is_null(Redis::get(...)))","depth":27,"bounds":{"left":0.18351063,"top":0.16360734,"width":0.09275266,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"check at the exact same millisecond. Both will then execute","depth":26,"bounds":{"left":0.12450133,"top":0.16201118,"width":0.21575798,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.2122673,"top":0.18276137,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and push duplicate metrics to Datadog.","depth":26,"bounds":{"left":0.22922207,"top":0.1811652,"width":0.09823803,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Fix:","depth":26,"bounds":{"left":0.12450133,"top":0.20989625,"width":0.019281914,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This should be replaced with a single atomic operation using Redis's","depth":26,"bounds":{"left":0.14378324,"top":0.20989625,"width":0.16821809,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"NX","depth":27,"bounds":{"left":0.31399602,"top":0.21149242,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Not eXists) and","depth":26,"bounds":{"left":0.12450133,"top":0.20989625,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"EX","depth":27,"bounds":{"left":0.13696809,"top":0.23064645,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Expire) flags, or Laravel’s built-in atomic cache locks:","depth":26,"bounds":{"left":0.14494681,"top":0.22905028,"width":0.13264628,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis::set($gaugeLockName, true, 'EX', 60, 'NX')","depth":27,"bounds":{"left":0.12649602,"top":0.24980047,"width":0.14361702,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.27210772,"top":0.2482043,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"3. Infinite Loops and Caller Contract","depth":23,"bounds":{"left":0.111369684,"top":0.2980846,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. Infinite Loops and Caller Contract","depth":24,"bounds":{"left":0.111369684,"top":0.29768556,"width":0.10571808,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Return Value Change:","depth":26,"bounds":{"left":0.12450133,"top":0.32482043,"width":0.055352394,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"bounds":{"left":0.18301196,"top":0.3264166,"width":0.06881649,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"previously returned whether it should iterate again based on staleness or chunk size. It now always returns","depth":26,"bounds":{"left":0.12450133,"top":0.32482043,"width":0.2237367,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"bounds":{"left":0.29438165,"top":0.34557062,"width":0.011968086,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(unless the selection is totally empty).","depth":26,"bounds":{"left":0.12450133,"top":0.34397447,"width":0.2130984,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.39185953,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the code calling this method relies on a","depth":26,"bounds":{"left":0.16456117,"top":0.39185953,"width":0.10305851,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"false","depth":27,"bounds":{"left":0.26961437,"top":0.3934557,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"return to pause, sleep, or gracefully terminate, changing this to an unconditional","depth":26,"bounds":{"left":0.12450133,"top":0.39185953,"width":0.22589761,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"bounds":{"left":0.26163563,"top":0.41260973,"width":0.011968086,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"could result in an aggressive \"busy loop\" that spikes CPU usage.","depth":26,"bounds":{"left":0.12450133,"top":0.41101357,"width":0.22290559,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"4. Performance Overhead","depth":23,"bounds":{"left":0.111369684,"top":0.48004788,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4. Performance Overhead","depth":24,"bounds":{"left":0.111369684,"top":0.47964883,"width":0.076130316,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Expensive Garbage Collection:","depth":26,"bounds":{"left":0.12450133,"top":0.5067837,"width":0.078457445,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Calling","depth":26,"bounds":{"left":0.20295878,"top":0.5067837,"width":0.019281914,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles();","depth":27,"bounds":{"left":0.22423537,"top":0.5083799,"width":0.059840426,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"on","depth":26,"bounds":{"left":0.28607047,"top":0.5067837,"width":0.00880984,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"every single chunk","depth":26,"bounds":{"left":0.29488033,"top":0.5067837,"width":0.045545213,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.","depth":26,"bounds":{"left":0.12450133,"top":0.5067837,"width":0.23420878,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Synchronous Metric Dispatch:","depth":26,"bounds":{"left":0.12450133,"top":0.5738228,"width":0.07712766,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the","depth":26,"bounds":{"left":0.20162898,"top":0.5738228,"width":0.015292553,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Datadog::increment","depth":27,"bounds":{"left":0.21891622,"top":0.575419,"width":0.053856384,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"or","depth":26,"bounds":{"left":0.27476728,"top":0.5738228,"width":0.0078125,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Datadog::gauge","depth":27,"bounds":{"left":0.28457448,"top":0.575419,"width":0.041888297,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.","depth":26,"bounds":{"left":0.12450133,"top":0.5738228,"width":0.23420878,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"5. Lack of Explicit Error Handling","depth":23,"bounds":{"left":0.111369684,"top":0.66201115,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5. Lack of Explicit Error Handling","depth":24,"bounds":{"left":0.111369684,"top":0.66161215,"width":0.094913565,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No","depth":26,"bounds":{"left":0.12450133,"top":0.688747,"width":0.008477394,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"try/catch","depth":27,"bounds":{"left":0.1349734,"top":0.6903432,"width":0.026928192,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Blocks:","depth":26,"bounds":{"left":0.16389628,"top":0.688747,"width":0.019614361,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The script executes complex network operations (","depth":26,"bounds":{"left":0.18351063,"top":0.688747,"width":0.12367021,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"loadDocuments","depth":27,"bounds":{"left":0.30917552,"top":0.6903432,"width":0.038896278,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.","depth":26,"bounds":{"left":0.12450133,"top":0.688747,"width":0.22888963,"height":0.09377494},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":22,"bounds":{"left":0.1087101,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":22,"bounds":{"left":0.124667555,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Redo","depth":22,"bounds":{"left":0.140625,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":21,"bounds":{"left":0.15658244,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":22,"bounds":{"left":0.17253989,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":21,"bounds":{"left":0.18849733,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXTextArea","text":"Ask Gemini","depth":21,"bounds":{"left":0.14594415,"top":0.86632085,"width":0.17021276,"height":0.01915403},"on_screen":true,"value":"Ask Gemini","help_text":"","role_description":"text entry area","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Ask Gemini","depth":22,"bounds":{"left":0.14594415,"top":0.8667199,"width":0.030418882,"height":0.018355945},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Upload & tools","depth":20,"bounds":{"left":0.12732713,"top":0.8567438,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Microphone","depth":20,"bounds":{"left":0.32413563,"top":0.86153233,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Send message","depth":20,"bounds":{"left":0.33211437,"top":0.8631285,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Your Jiminny chats aren’t used to improve our models. Gemini is AI. It can make mistakes, so double check it.","depth":17,"bounds":{"left":0.102726065,"top":0.9173983,"width":0.21775267,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Your privacy & Gemini Opens in a new window","depth":17,"bounds":{"left":0.32047874,"top":0.9173983,"width":0.043882977,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Your privacy & Gemini","depth":18,"bounds":{"left":0.32047874,"top":0.9173983,"width":0.043882977,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Opens in a new window","depth":19,"bounds":{"left":0.079288565,"top":0.91660017,"width":0.04720745,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Summarize page","depth":7,"bounds":{"left":0.08494016,"top":0.95730245,"width":0.053523935,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Summarize page","depth":9,"bounds":{"left":0.09059176,"top":0.96249,"width":0.042220745,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Skip to content","depth":6,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to content","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open menu","depth":10,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Homepage (g then d)","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"jiminny","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"jiminny","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"app","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"app","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Search or jump to…","depth":9,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Type","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to search","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Chat with Copilot","depth":10,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Open Copilot…","depth":9,"on_screen":false,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Create new...","depth":9,"on_screen":false,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"All issues(g then i)","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"All pull requests","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"All repositories","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"You have unread notifications(g then n)","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open user navigation menu","depth":9,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Repository navigation","depth":9,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Repository navigation","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Code","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Code","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Pull requests (31)","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pull requests","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"31","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Agents","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Agents","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Actions","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Actions","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Wiki","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Wiki","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Security and quality","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Security and quality","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Insights","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Insights","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Settings","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Settings","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"JY-20676 delete AJ reports related objects #12098 Edit title","depth":13,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"#","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"12098","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Edit title","depth":14,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Checks pending","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Checks pending","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Code","depth":13,"on_screen":false,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Code","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Open","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"LakyLak","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"LakyLak","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"wants to merge 20 commits into","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"master","depth":15,"on_screen":false,"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"master","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"from","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"JY-20676-delete-report-related-objects","depth":16,"on_screen":false,"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20676-delete-report-related-objects","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy head branch name to clipboard","depth":16,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Lines changed: 1698 additions & 1471 deletions","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Conversation (9)","depth":16,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Conversation","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"9","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Commits (20)","depth":16,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Commits","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"20","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
6384488757690210219
|
-1052787213578504420
|
click
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage.
4. Performance Overhead
4. Performance Overhead
Expensive Garbage Collection:
Calling
gc_collect_cycles();
on
every single chunk
can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.
Synchronous Metric Dispatch:
If the
Datadog::increment
or
Datadog::gauge
methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.
5. Lack of Explicit Error Handling
5. Lack of Explicit Error Handling
No
try/catch
Blocks:
The script executes complex network operations (
loadDocuments
, Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.
Good response
Bad response
Redo
Share & export
Copy
Show more options
Ask Gemini
Ask Gemini
Upload & tools
Microphone
Send message
Your Jiminny chats aren’t used to improve our models. Gemini is AI. It can make mistakes, so double check it.
Your privacy & Gemini Opens in a new window
Your privacy & Gemini
Opens in a new window
Summarize page
Summarize page
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
app
app
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
All issues(g then i)
All pull requests
All repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Pull requests (31)
Pull requests
(
31
)
Agents
Agents
Actions
Actions
Wiki
Wiki
Security and quality
Security and quality
Insights
Insights
Settings
Settings
JY-20676 delete AJ reports related objects #12098 Edit title
JY-20676 delete AJ reports related objects
#
12098
Edit title
Checks pending
Checks pending
Code
Code
Open
LakyLak
LakyLak
wants to merge 20 commits into
master
master
from
JY-20676-delete-report-related-objects
JY-20676-delete-report-related-objects
Copy head branch name to clipboard
Lines changed: 1698 additions & 1471 deletions
Conversation (9)
Conversation
(
9
)
Commits (20)
Commits
(
20...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
64673
|
2264
|
25
|
2026-05-20T14:11:28.843329+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779286288843_m1.jpg...
|
Firefox
|
JY-20676 delete AJ reports related objects by Laky JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app — Work...
|
1
|
github.com/jiminny/app/pull/12098
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
8852295099697676704
|
7230047640003280981
|
visual_change
|
hybrid
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
SlackFileEditViewGoHistoryWindowHelpAPPDOCKER• ₴1DEV (-zsh)₴2Fixedof 5690 files in 78.144 seconds,60.00MBmemoryusedWhat's next:Try Docker Debug for seamless, persistentdebugging tools in any containeror image →Learn more at [URL_WITH_CREDENTIALS] ~/jiminny/app(JY-20676-delete-report-related-obihotos-3-001\(2\)/talon-b.png/Usere /ulene/Daualaade/Dhatne 2 A0100s/balanh UCTGlukd5-3-/USEPSlukcPhot/USESlackLukas-Photos-3-001\(2\)/dr-lic-b.png/Users/lukas/Downloads/Photos-3-001(2)/dr-lic-b.HEIC/Users/lukas/Downloads/Photos-3-001(2)/dr-lic-b.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obs-3-001\(2)/gtp.pngWarning: /Users/lukas/Downloads/Photos-3-001(2)/gtp.HEIC not a valid file - skippinglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obs-3-001\(2\)/gtp.png/Users/lukas/Downloads/Photos-3-001(2)/gtp.heif/Users/lukas/Downloads/Photos-3-001(2)/gtp.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny~/jiminny/app (JY-20676-delete-report-related-ob:/Photos-3-001\(2\)/talon-m-f.png/Users/lukas/Downloads/Photos-3-001(2)/talon-m-f.HEIC/Users/lukas/Downloads/Photos-3-001(2)/talon-m-f.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/Jiminny/appJY-20676-delete-report-related-ob]/Photos-3-001\(2\)/talon-m-b.png/Users/lukas/Downloads/Photos-3-001(2)/talon-m-b.HEIC/Users/lukas/Downloads/Photos-3-001(2)/talon-m-b.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app(JY-20676-delete-report-related-obHomeDMsActivity• 0(ahol•Al chapter • 49 m left100% <28•Wed 20 May 17:11:28Describe what you are looking forDirect messages ~UnreadsQFind a DMNikolay Yankov8 minsаз сьм готов с моята част, местиги в QА като видиш коментаритеAneliya Angelova12:19 PMYou: Ако се наложи ще му пишаStoyan TanevYesterdayNikolay ...MessagesAdd canvasMore vпушнах за saven search в сьщиябранчToday ~+Saved for later • Due 2 hours agoако искаш виж на планетатаNikolay Yankov 1:01 PMЛукас, има коментари от ClaudeLukas Kovalik 2:35 PMНики при таска за owner rolesтрябва ла махнем recorder & [EMAIL] DimitrovaYesterdayедно е ок, няма нужда да гиделим. Той като влезе на едит щ...Vasil Vasilev XблагодаряYesterdayStoyan TomovYou: ако се наложи ще гопреместимMondayTodor StamatovThursdayNГаля иска да се махнеNikolay Yankov 2:38 PMАха, добре ,ще го махна да я нямав списькаNikolay Yankov 3:25 PMмахнах гоNewNikolay Yankov 5:03 PMаз сьм готов с моята част, местиги в QА като видиш коментаритеMessage Nikolay Yankov+Aa...
|
64671
|
NULL
|
NULL
|
NULL
|
|
64674
|
2265
|
53
|
2026-05-20T14:11:31.187750+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779286291187_m2.jpg...
|
Firefox
|
JY-20676 delete AJ reports related objects by Laky JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app — Work...
|
1
|
github.com/jiminny/app/pull/12098
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Close tab
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage.
4. Performance Overhead
4. Performance Overhead
Expensive Garbage Collection:
Calling
gc_collect_cycles();
on
every single chunk
can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.
Synchronous Metric Dispatch:
If the
Datadog::increment
or
Datadog::gauge
methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.
5. Lack of Explicit Error Handling
5. Lack of Explicit Error Handling
No
try/catch
Blocks:
The script executes complex network operations (
loadDocuments
, Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.
Good response
Bad response
Redo
Share & export
Copy
Show more options
Ask Gemini
Ask Gemini
Upload & tools
Microphone
Send message
Your Jiminny chats aren’t used to improve our models. Gemini is AI. It can make mistakes, so double check it.
Your privacy & Gemini Opens in a new window
Your privacy & Gemini
Opens in a new window
Summarize page
Summarize page
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
app
app
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
All issues(g then i)
All pull requests
All repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Pull requests (31)...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.10721409,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.15791224,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.1245012,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.1796875,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.15722266,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.12699468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":4,"bounds":{"left":0.0,"top":0.21548285,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":5,"bounds":{"left":0.013297873,"top":0.22665602,"width":0.10139628,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"bounds":{"left":0.0,"top":0.2482043,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.25937748,"width":0.18068483,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.28092578,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.29209897,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.31364724,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.32482043,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.3463687,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.3575419,"width":0.15824468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.38068634,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"bounds":{"left":0.35854387,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"bounds":{"left":0.37051198,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Main menu","depth":13,"bounds":{"left":0.08228058,"top":0.09736632,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New chat","depth":12,"bounds":{"left":0.09823803,"top":0.10215483,"width":0.028091755,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":15,"bounds":{"left":0.1008976,"top":0.10654429,"width":0.02144282,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Chat","depth":13,"bounds":{"left":0.35854387,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":13,"bounds":{"left":0.37051198,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"bounds":{"left":0.079288565,"top":0.14445332,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"bounds":{"left":0.079288565,"top":0.14684756,"width":0.1200133,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"You said I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a concise summary of the changes made in the code selection:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Refactoring and Dependencies","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Refactoring and Dependencies","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Replaced Interface with Implementation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusInterface","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusManager","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in the imports and constructor dependency injection.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Redis Support:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Imported","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Illuminate\\Support\\Facades\\Redis","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to handle a newly introduced memory-logging lock mechanism.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Traits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WorkerIdTrait","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", along with the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"worker","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"field tracking in the final logs.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Loop and Worker Lifecycle Adjustments","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Loop and Worker Lifecycle Adjustments","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Worker Auto-Restart:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Deleted the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property, the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"constant, and the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"shouldIterateAgain()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method. The script no longer tracks execution iterations or triggers a self-managed worker restart.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Simplified Chunk Method Return:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Modified","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to always return","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Priority Handling for Throttled Entities","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Priority Handling for Throttled Entities","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Stale Record Elevation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Updated the logic in","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"rescheduleThrottledEntities()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to check if a throttled entity group is classified as \"stale\". If it is, the entities are rescheduled using","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"QueuePriorityEnum::NORMAL","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"; otherwise, they keep their respective queue priority.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Metrics, Logging, and Environment Handling","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Metrics, Logging, and Environment Handling","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Environment Tracking:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Introduced a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$environment","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property (defaulting to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'local'","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") and a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"setEnvironment()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method to dynamically assign it.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Memory Monitoring via Datadog Gauge:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method that submits peak memory allocation to Datadog as a gauge metric (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"jiminny.reindex-memory-usage","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis Lock for Metrics Throttling:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Implemented a Redis-backed lock (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"%s-process-manager-lock","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") with a 60-second expiration inside","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to prevent over-reporting metrics.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Log Payload Update:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added total processed items (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$updatedDocuments + $deletedDocuments","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") to the final information log.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":23,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said are there any issues or potential bottlenecks","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"are there any issues or potential bottlenecks","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a breakdown of the potential issues to look out for:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"1. Memory Leak Risk (OOM Crashes)","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. Memory Leak Risk (OOM Crashes)","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Restart Logic:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The most significant change is the removal of the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Dependency Leaks:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If any underlying service (like the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ElasticSearchClient","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", ORM, or Datadog helper) retains references to processed entities,","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"bounds":{"left":0.26811835,"top":0.0,"width":0.056848403,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23138298,"height":0.05546688},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Hardcoded Limits:","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.047041222,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The code still includes","depth":26,"bounds":{"left":0.17154256,"top":0.0,"width":0.05651596,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ini_set('memory_limit', '512M');","depth":27,"bounds":{"left":0.23005319,"top":0.0,"width":0.095744684,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23055187,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"2. Race Condition in Redis Lock","depth":23,"bounds":{"left":0.111369684,"top":0.06823623,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. Race Condition in Redis Lock","depth":24,"bounds":{"left":0.111369684,"top":0.06783719,"width":0.09125665,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Non-Atomic Operations:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.062333778,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The","depth":26,"bounds":{"left":0.18683511,"top":0.09497207,"width":0.011635638,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage","depth":27,"bounds":{"left":0.20046543,"top":0.096568234,"width":0.041888297,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method uses three distinct steps to handle the lock:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.22722739,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"get()","depth":27,"bounds":{"left":0.14860372,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":26,"bounds":{"left":0.16555852,"top":0.11412609,"width":0.002493351,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.17004654,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", and","depth":26,"bounds":{"left":0.18700133,"top":0.11412609,"width":0.013131649,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"expire()","depth":27,"bounds":{"left":0.20212767,"top":0.11572227,"width":0.023936171,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.22805852,"top":0.11412609,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If multiple workers process the same","depth":26,"bounds":{"left":0.16456117,"top":0.14285715,"width":0.09275266,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$this->updateTarget","depth":27,"bounds":{"left":0.25930852,"top":0.14445332,"width":0.056848403,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"concurrently, they can both pass the","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"if (! is_null(Redis::get(...)))","depth":27,"bounds":{"left":0.18351063,"top":0.16360734,"width":0.09275266,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"check at the exact same millisecond. Both will then execute","depth":26,"bounds":{"left":0.12450133,"top":0.16201118,"width":0.21575798,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.2122673,"top":0.18276137,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and push duplicate metrics to Datadog.","depth":26,"bounds":{"left":0.22922207,"top":0.1811652,"width":0.09823803,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Fix:","depth":26,"bounds":{"left":0.12450133,"top":0.20989625,"width":0.019281914,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This should be replaced with a single atomic operation using Redis's","depth":26,"bounds":{"left":0.14378324,"top":0.20989625,"width":0.16821809,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"NX","depth":27,"bounds":{"left":0.31399602,"top":0.21149242,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Not eXists) and","depth":26,"bounds":{"left":0.12450133,"top":0.20989625,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"EX","depth":27,"bounds":{"left":0.13696809,"top":0.23064645,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Expire) flags, or Laravel’s built-in atomic cache locks:","depth":26,"bounds":{"left":0.14494681,"top":0.22905028,"width":0.13264628,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis::set($gaugeLockName, true, 'EX', 60, 'NX')","depth":27,"bounds":{"left":0.12649602,"top":0.24980047,"width":0.14361702,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.27210772,"top":0.2482043,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"3. Infinite Loops and Caller Contract","depth":23,"bounds":{"left":0.111369684,"top":0.2980846,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. Infinite Loops and Caller Contract","depth":24,"bounds":{"left":0.111369684,"top":0.29768556,"width":0.10571808,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Return Value Change:","depth":26,"bounds":{"left":0.12450133,"top":0.32482043,"width":0.055352394,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"bounds":{"left":0.18301196,"top":0.3264166,"width":0.06881649,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"previously returned whether it should iterate again based on staleness or chunk size. It now always returns","depth":26,"bounds":{"left":0.12450133,"top":0.32482043,"width":0.2237367,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"bounds":{"left":0.29438165,"top":0.34557062,"width":0.011968086,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(unless the selection is totally empty).","depth":26,"bounds":{"left":0.12450133,"top":0.34397447,"width":0.2130984,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.39185953,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the code calling this method relies on a","depth":26,"bounds":{"left":0.16456117,"top":0.39185953,"width":0.10305851,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"false","depth":27,"bounds":{"left":0.26961437,"top":0.3934557,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"return to pause, sleep, or gracefully terminate, changing this to an unconditional","depth":26,"bounds":{"left":0.12450133,"top":0.39185953,"width":0.22589761,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"bounds":{"left":0.26163563,"top":0.41260973,"width":0.011968086,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"could result in an aggressive \"busy loop\" that spikes CPU usage.","depth":26,"bounds":{"left":0.12450133,"top":0.41101357,"width":0.22290559,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"4. Performance Overhead","depth":23,"bounds":{"left":0.111369684,"top":0.48004788,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4. Performance Overhead","depth":24,"bounds":{"left":0.111369684,"top":0.47964883,"width":0.076130316,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Expensive Garbage Collection:","depth":26,"bounds":{"left":0.12450133,"top":0.5067837,"width":0.078457445,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Calling","depth":26,"bounds":{"left":0.20295878,"top":0.5067837,"width":0.019281914,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles();","depth":27,"bounds":{"left":0.22423537,"top":0.5083799,"width":0.059840426,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"on","depth":26,"bounds":{"left":0.28607047,"top":0.5067837,"width":0.00880984,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"every single chunk","depth":26,"bounds":{"left":0.29488033,"top":0.5067837,"width":0.045545213,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.","depth":26,"bounds":{"left":0.12450133,"top":0.5067837,"width":0.23420878,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Synchronous Metric Dispatch:","depth":26,"bounds":{"left":0.12450133,"top":0.5738228,"width":0.07712766,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the","depth":26,"bounds":{"left":0.20162898,"top":0.5738228,"width":0.015292553,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Datadog::increment","depth":27,"bounds":{"left":0.21891622,"top":0.575419,"width":0.053856384,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"or","depth":26,"bounds":{"left":0.27476728,"top":0.5738228,"width":0.0078125,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Datadog::gauge","depth":27,"bounds":{"left":0.28457448,"top":0.575419,"width":0.041888297,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.","depth":26,"bounds":{"left":0.12450133,"top":0.5738228,"width":0.23420878,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"5. Lack of Explicit Error Handling","depth":23,"bounds":{"left":0.111369684,"top":0.66201115,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5. Lack of Explicit Error Handling","depth":24,"bounds":{"left":0.111369684,"top":0.66161215,"width":0.094913565,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No","depth":26,"bounds":{"left":0.12450133,"top":0.688747,"width":0.008477394,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"try/catch","depth":27,"bounds":{"left":0.1349734,"top":0.6903432,"width":0.026928192,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Blocks:","depth":26,"bounds":{"left":0.16389628,"top":0.688747,"width":0.019614361,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The script executes complex network operations (","depth":26,"bounds":{"left":0.18351063,"top":0.688747,"width":0.12367021,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"loadDocuments","depth":27,"bounds":{"left":0.30917552,"top":0.6903432,"width":0.038896278,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.","depth":26,"bounds":{"left":0.12450133,"top":0.688747,"width":0.22888963,"height":0.09377494},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":22,"bounds":{"left":0.1087101,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":22,"bounds":{"left":0.124667555,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Redo","depth":22,"bounds":{"left":0.140625,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":21,"bounds":{"left":0.15658244,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":22,"bounds":{"left":0.17253989,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":21,"bounds":{"left":0.18849733,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXTextArea","text":"Ask Gemini","depth":21,"bounds":{"left":0.14594415,"top":0.86632085,"width":0.17021276,"height":0.01915403},"on_screen":true,"value":"Ask Gemini","help_text":"","role_description":"text entry area","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Ask Gemini","depth":22,"bounds":{"left":0.14594415,"top":0.8667199,"width":0.030418882,"height":0.018355945},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Upload & tools","depth":20,"bounds":{"left":0.12732713,"top":0.8567438,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Microphone","depth":20,"bounds":{"left":0.32413563,"top":0.86153233,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Send message","depth":20,"bounds":{"left":0.33211437,"top":0.8631285,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Your Jiminny chats aren’t used to improve our models. Gemini is AI. It can make mistakes, so double check it.","depth":17,"bounds":{"left":0.102726065,"top":0.9173983,"width":0.21775267,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Your privacy & Gemini Opens in a new window","depth":17,"bounds":{"left":0.32047874,"top":0.9173983,"width":0.043882977,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Your privacy & Gemini","depth":18,"bounds":{"left":0.32047874,"top":0.9173983,"width":0.043882977,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Opens in a new window","depth":19,"bounds":{"left":0.079288565,"top":0.91660017,"width":0.04720745,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Summarize page","depth":7,"bounds":{"left":0.08494016,"top":0.95730245,"width":0.053523935,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Summarize page","depth":9,"bounds":{"left":0.09059176,"top":0.96249,"width":0.042220745,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Skip to content","depth":6,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to content","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open menu","depth":10,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Homepage (g then d)","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"jiminny","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"jiminny","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"app","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"app","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Search or jump to…","depth":9,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Type","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to search","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Chat with Copilot","depth":10,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Open Copilot…","depth":9,"on_screen":false,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Create new...","depth":9,"on_screen":false,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"All issues(g then i)","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"All pull requests","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"All repositories","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"You have unread notifications(g then n)","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open user navigation menu","depth":9,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Repository navigation","depth":9,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Repository navigation","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Code","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Code","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Pull requests (31)","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
5088377340139915549
|
-1052797109183088740
|
visual_change
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Close tab
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage.
4. Performance Overhead
4. Performance Overhead
Expensive Garbage Collection:
Calling
gc_collect_cycles();
on
every single chunk
can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.
Synchronous Metric Dispatch:
If the
Datadog::increment
or
Datadog::gauge
methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.
5. Lack of Explicit Error Handling
5. Lack of Explicit Error Handling
No
try/catch
Blocks:
The script executes complex network operations (
loadDocuments
, Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.
Good response
Bad response
Redo
Share & export
Copy
Show more options
Ask Gemini
Ask Gemini
Upload & tools
Microphone
Send message
Your Jiminny chats aren’t used to improve our models. Gemini is AI. It can make mistakes, so double check it.
Your privacy & Gemini Opens in a new window
Your privacy & Gemini
Opens in a new window
Summarize page
Summarize page
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
app
app
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
All issues(g then i)
All pull requests
All repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Pull requests (31)...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
64675
|
2264
|
26
|
2026-05-20T14:11:31.875520+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779286291875_m1.jpg...
|
Firefox
|
JY-20676 delete AJ reports related objects by Laky JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app — Work...
|
1
|
github.com/jiminny/app/pull/12098
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Allow owner's role to be selected when setting Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
github.com
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Close tab
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:...
|
[{"role":"AXStaticText","text& [{"role":"AXStaticText","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"github.com","depth":4,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Main menu","depth":13,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New chat","depth":12,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Chat","depth":13,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":13,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"You said I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a concise summary of the changes made in the code selection:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Refactoring and Dependencies","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Refactoring and Dependencies","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Replaced Interface with Implementation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusInterface","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusManager","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in the imports and constructor dependency injection.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Redis Support:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Imported","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Illuminate\\Support\\Facades\\Redis","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to handle a newly introduced memory-logging lock mechanism.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Traits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WorkerIdTrait","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", along with the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"worker","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"field tracking in the final logs.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Loop and Worker Lifecycle Adjustments","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Loop and Worker Lifecycle Adjustments","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Worker Auto-Restart:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Deleted the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property, the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"constant, and the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"shouldIterateAgain()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method. The script no longer tracks execution iterations or triggers a self-managed worker restart.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Simplified Chunk Method Return:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Modified","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to always return","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Priority Handling for Throttled Entities","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Priority Handling for Throttled Entities","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Stale Record Elevation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Updated the logic in","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"rescheduleThrottledEntities()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to check if a throttled entity group is classified as \"stale\". If it is, the entities are rescheduled using","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"QueuePriorityEnum::NORMAL","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"; otherwise, they keep their respective queue priority.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Metrics, Logging, and Environment Handling","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Metrics, Logging, and Environment Handling","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Environment Tracking:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Introduced a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$environment","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property (defaulting to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'local'","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") and a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"setEnvironment()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method to dynamically assign it.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Memory Monitoring via Datadog Gauge:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method that submits peak memory allocation to Datadog as a gauge metric (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"jiminny.reindex-memory-usage","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis Lock for Metrics Throttling:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Implemented a Redis-backed lock (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"%s-process-manager-lock","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") with a 60-second expiration inside","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to prevent over-reporting metrics.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Log Payload Update:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added total processed items (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$updatedDocuments + $deletedDocuments","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") to the final information log.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":23,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said are there any issues or potential bottlenecks","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"are there any issues or potential bottlenecks","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a breakdown of the potential issues to look out for:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"1. Memory Leak Risk (OOM Crashes)","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. Memory Leak Risk (OOM Crashes)","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Restart Logic:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The most significant change is the removal of the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Dependency Leaks:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If any underlying service (like the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ElasticSearchClient","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", ORM, or Datadog helper) retains references to processed entities,","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Hardcoded Limits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The code still includes","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ini_set('memory_limit', '512M');","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"2. Race Condition in Redis Lock","depth":23,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. Race Condition in Redis Lock","depth":24,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Non-Atomic Operations:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method uses three distinct steps to handle the lock:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"get()","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", and","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"expire()","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If multiple workers process the same","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$this->updateTarget","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"concurrently, they can both pass the","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"if (! is_null(Redis::get(...)))","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"check at the exact same millisecond. Both will then execute","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and push duplicate metrics to Datadog.","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Fix:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This should be replaced with a single atomic operation using Redis's","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"NX","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Not eXists) and","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"EX","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Expire) flags, or Laravel’s built-in atomic cache locks:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis::set($gaugeLockName, true, 'EX', 60, 'NX')","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"3. Infinite Loops and Caller Contract","depth":23,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. Infinite Loops and Caller Contract","depth":24,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Return Value Change:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-3501240900674981015
|
-1061874711537616936
|
visual_change
|
accessibility
|
NULL
|
Allow owner's role to be selected when setting Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
github.com
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Close tab
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
64676
|
2264
|
27
|
2026-05-20T14:11:33.372418+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779286293372_m1.jpg...
|
Firefox
|
JY-20676 delete AJ reports related objects by Laky JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app — Work...
|
1
|
github.com/jiminny/app/pull/12098
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
JY-20676 delete AJ reports related objects by Laky JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
github.com
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage.
4. Performance Overhead
4. Performance Overhead
Expensive Garbage Collection:
Calling
gc_collect_cycles();
on
every single chunk
can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.
Synchronous Metric Dispatch:...
|
[{"role":"AXStaticText","text& [{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"github.com","depth":4,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Main menu","depth":13,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New chat","depth":12,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Chat","depth":13,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":13,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"You said I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a concise summary of the changes made in the code selection:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Refactoring and Dependencies","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Refactoring and Dependencies","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Replaced Interface with Implementation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusInterface","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusManager","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in the imports and constructor dependency injection.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Redis Support:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Imported","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Illuminate\\Support\\Facades\\Redis","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to handle a newly introduced memory-logging lock mechanism.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Traits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WorkerIdTrait","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", along with the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"worker","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"field tracking in the final logs.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Loop and Worker Lifecycle Adjustments","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Loop and Worker Lifecycle Adjustments","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Worker Auto-Restart:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Deleted the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property, the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"constant, and the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"shouldIterateAgain()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method. The script no longer tracks execution iterations or triggers a self-managed worker restart.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Simplified Chunk Method Return:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Modified","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to always return","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Priority Handling for Throttled Entities","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Priority Handling for Throttled Entities","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Stale Record Elevation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Updated the logic in","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"rescheduleThrottledEntities()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to check if a throttled entity group is classified as \"stale\". If it is, the entities are rescheduled using","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"QueuePriorityEnum::NORMAL","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"; otherwise, they keep their respective queue priority.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Metrics, Logging, and Environment Handling","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Metrics, Logging, and Environment Handling","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Environment Tracking:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Introduced a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$environment","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property (defaulting to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'local'","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") and a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"setEnvironment()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method to dynamically assign it.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Memory Monitoring via Datadog Gauge:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method that submits peak memory allocation to Datadog as a gauge metric (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"jiminny.reindex-memory-usage","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis Lock for Metrics Throttling:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Implemented a Redis-backed lock (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"%s-process-manager-lock","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") with a 60-second expiration inside","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to prevent over-reporting metrics.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Log Payload Update:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added total processed items (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$updatedDocuments + $deletedDocuments","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") to the final information log.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":23,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said are there any issues or potential bottlenecks","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"are there any issues or potential bottlenecks","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a breakdown of the potential issues to look out for:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"1. Memory Leak Risk (OOM Crashes)","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. Memory Leak Risk (OOM Crashes)","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Restart Logic:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The most significant change is the removal of the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Dependency Leaks:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If any underlying service (like the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ElasticSearchClient","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", ORM, or Datadog helper) retains references to processed entities,","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Hardcoded Limits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The code still includes","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ini_set('memory_limit', '512M');","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"2. Race Condition in Redis Lock","depth":23,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. Race Condition in Redis Lock","depth":24,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Non-Atomic Operations:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method uses three distinct steps to handle the lock:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"get()","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", and","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"expire()","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If multiple workers process the same","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$this->updateTarget","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"concurrently, they can both pass the","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"if (! is_null(Redis::get(...)))","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"check at the exact same millisecond. Both will then execute","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and push duplicate metrics to Datadog.","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Fix:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This should be replaced with a single atomic operation using Redis's","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"NX","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Not eXists) and","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"EX","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Expire) flags, or Laravel’s built-in atomic cache locks:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis::set($gaugeLockName, true, 'EX', 60, 'NX')","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"3. Infinite Loops and Caller Contract","depth":23,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. Infinite Loops and Caller Contract","depth":24,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Return Value Change:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"previously returned whether it should iterate again based on staleness or chunk size. It now always returns","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(unless the selection is totally empty).","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the code calling this method relies on a","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"false","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"return to pause, sleep, or gracefully terminate, changing this to an unconditional","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"could result in an aggressive \"busy loop\" that spikes CPU usage.","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"4. Performance Overhead","depth":23,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4. Performance Overhead","depth":24,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Expensive Garbage Collection:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Calling","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles();","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"on","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"every single chunk","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Synchronous Metric Dispatch:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-1588784419904871481
|
-1079889110047033380
|
click
|
accessibility
|
NULL
|
JY-20676 delete AJ reports related objects by Laky JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
github.com
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage.
4. Performance Overhead
4. Performance Overhead
Expensive Garbage Collection:
Calling
gc_collect_cycles();
on
every single chunk
can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.
Synchronous Metric Dispatch:...
|
64675
|
NULL
|
NULL
|
NULL
|
|
64677
|
2265
|
54
|
2026-05-20T14:11:33.353602+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779286293353_m2.jpg...
|
Firefox
|
JY-20676 delete AJ reports related objects by Laky JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app — Work...
|
1
|
github.com/jiminny/app/pull/12098
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
JY-20676 delete AJ reports related objects by Laky JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
github.com
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage.
4. Performance Overhead
4. Performance Overhead
Expensive Garbage Collection:
Calling
gc_collect_cycles();
on
every single chunk
can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.
Synchronous Metric Dispatch:
If the
Datadog::increment
or
Datadog::gauge
methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.
5. Lack of Explicit Error Handling
5. Lack of Explicit Error Handling
No
try/catch
Blocks:
The script executes complex network operations (...
|
[{"role":"AXStaticText","text& [{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"bounds":{"left":0.08361037,"top":0.1245012,"width":0.08427527,"height":0.021548284},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"github.com","depth":4,"bounds":{"left":0.08361037,"top":0.14604948,"width":0.019614361,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.10721409,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.15791224,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.1245012,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.1796875,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.12699468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":4,"bounds":{"left":0.0,"top":0.21548285,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":5,"bounds":{"left":0.013297873,"top":0.22665602,"width":0.10139628,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"bounds":{"left":0.0,"top":0.2482043,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.25937748,"width":0.18068483,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.28092578,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.29209897,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.31364724,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.32482043,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.3463687,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.3575419,"width":0.15824468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.38068634,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"bounds":{"left":0.35854387,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"bounds":{"left":0.37051198,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Main menu","depth":13,"bounds":{"left":0.08228058,"top":0.09736632,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New chat","depth":12,"bounds":{"left":0.09823803,"top":0.10215483,"width":0.028091755,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":15,"bounds":{"left":0.1008976,"top":0.10654429,"width":0.02144282,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Chat","depth":13,"bounds":{"left":0.35854387,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":13,"bounds":{"left":0.37051198,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"bounds":{"left":0.079288565,"top":0.14445332,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"bounds":{"left":0.079288565,"top":0.14684756,"width":0.1200133,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"You said I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a concise summary of the changes made in the code selection:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Refactoring and Dependencies","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Refactoring and Dependencies","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Replaced Interface with Implementation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusInterface","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusManager","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in the imports and constructor dependency injection.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Redis Support:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Imported","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Illuminate\\Support\\Facades\\Redis","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to handle a newly introduced memory-logging lock mechanism.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Traits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WorkerIdTrait","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", along with the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"worker","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"field tracking in the final logs.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Loop and Worker Lifecycle Adjustments","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Loop and Worker Lifecycle Adjustments","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Worker Auto-Restart:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Deleted the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property, the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"constant, and the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"shouldIterateAgain()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method. The script no longer tracks execution iterations or triggers a self-managed worker restart.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Simplified Chunk Method Return:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Modified","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to always return","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Priority Handling for Throttled Entities","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Priority Handling for Throttled Entities","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Stale Record Elevation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Updated the logic in","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"rescheduleThrottledEntities()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to check if a throttled entity group is classified as \"stale\". If it is, the entities are rescheduled using","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"QueuePriorityEnum::NORMAL","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"; otherwise, they keep their respective queue priority.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Metrics, Logging, and Environment Handling","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Metrics, Logging, and Environment Handling","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Environment Tracking:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Introduced a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$environment","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property (defaulting to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'local'","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") and a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"setEnvironment()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method to dynamically assign it.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Memory Monitoring via Datadog Gauge:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method that submits peak memory allocation to Datadog as a gauge metric (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"jiminny.reindex-memory-usage","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis Lock for Metrics Throttling:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Implemented a Redis-backed lock (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"%s-process-manager-lock","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") with a 60-second expiration inside","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to prevent over-reporting metrics.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Log Payload Update:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added total processed items (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$updatedDocuments + $deletedDocuments","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") to the final information log.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":23,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said are there any issues or potential bottlenecks","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"are there any issues or potential bottlenecks","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a breakdown of the potential issues to look out for:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"1. Memory Leak Risk (OOM Crashes)","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. Memory Leak Risk (OOM Crashes)","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Restart Logic:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The most significant change is the removal of the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Dependency Leaks:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If any underlying service (like the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ElasticSearchClient","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", ORM, or Datadog helper) retains references to processed entities,","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"bounds":{"left":0.26811835,"top":0.0,"width":0.056848403,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23138298,"height":0.05546688},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Hardcoded Limits:","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.047041222,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The code still includes","depth":26,"bounds":{"left":0.17154256,"top":0.0,"width":0.05651596,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ini_set('memory_limit', '512M');","depth":27,"bounds":{"left":0.23005319,"top":0.0,"width":0.095744684,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23055187,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"2. Race Condition in Redis Lock","depth":23,"bounds":{"left":0.111369684,"top":0.06823623,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. Race Condition in Redis Lock","depth":24,"bounds":{"left":0.111369684,"top":0.06783719,"width":0.09125665,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Non-Atomic Operations:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.062333778,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The","depth":26,"bounds":{"left":0.18683511,"top":0.09497207,"width":0.011635638,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage","depth":27,"bounds":{"left":0.20046543,"top":0.096568234,"width":0.041888297,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method uses three distinct steps to handle the lock:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.22722739,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"get()","depth":27,"bounds":{"left":0.14860372,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":26,"bounds":{"left":0.16555852,"top":0.11412609,"width":0.002493351,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.17004654,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", and","depth":26,"bounds":{"left":0.18700133,"top":0.11412609,"width":0.013131649,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"expire()","depth":27,"bounds":{"left":0.20212767,"top":0.11572227,"width":0.023936171,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.22805852,"top":0.11412609,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If multiple workers process the same","depth":26,"bounds":{"left":0.16456117,"top":0.14285715,"width":0.09275266,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$this->updateTarget","depth":27,"bounds":{"left":0.25930852,"top":0.14445332,"width":0.056848403,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"concurrently, they can both pass the","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"if (! is_null(Redis::get(...)))","depth":27,"bounds":{"left":0.18351063,"top":0.16360734,"width":0.09275266,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"check at the exact same millisecond. Both will then execute","depth":26,"bounds":{"left":0.12450133,"top":0.16201118,"width":0.21575798,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.2122673,"top":0.18276137,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and push duplicate metrics to Datadog.","depth":26,"bounds":{"left":0.22922207,"top":0.1811652,"width":0.09823803,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Fix:","depth":26,"bounds":{"left":0.12450133,"top":0.20989625,"width":0.019281914,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This should be replaced with a single atomic operation using Redis's","depth":26,"bounds":{"left":0.14378324,"top":0.20989625,"width":0.16821809,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"NX","depth":27,"bounds":{"left":0.31399602,"top":0.21149242,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Not eXists) and","depth":26,"bounds":{"left":0.12450133,"top":0.20989625,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"EX","depth":27,"bounds":{"left":0.13696809,"top":0.23064645,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Expire) flags, or Laravel’s built-in atomic cache locks:","depth":26,"bounds":{"left":0.14494681,"top":0.22905028,"width":0.13264628,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis::set($gaugeLockName, true, 'EX', 60, 'NX')","depth":27,"bounds":{"left":0.12649602,"top":0.24980047,"width":0.14361702,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.27210772,"top":0.2482043,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"3. Infinite Loops and Caller Contract","depth":23,"bounds":{"left":0.111369684,"top":0.2980846,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. Infinite Loops and Caller Contract","depth":24,"bounds":{"left":0.111369684,"top":0.29768556,"width":0.10571808,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Return Value Change:","depth":26,"bounds":{"left":0.12450133,"top":0.32482043,"width":0.055352394,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"bounds":{"left":0.18301196,"top":0.3264166,"width":0.06881649,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"previously returned whether it should iterate again based on staleness or chunk size. It now always returns","depth":26,"bounds":{"left":0.12450133,"top":0.32482043,"width":0.2237367,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"bounds":{"left":0.29438165,"top":0.34557062,"width":0.011968086,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(unless the selection is totally empty).","depth":26,"bounds":{"left":0.12450133,"top":0.34397447,"width":0.2130984,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.39185953,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the code calling this method relies on a","depth":26,"bounds":{"left":0.16456117,"top":0.39185953,"width":0.10305851,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"false","depth":27,"bounds":{"left":0.26961437,"top":0.3934557,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"return to pause, sleep, or gracefully terminate, changing this to an unconditional","depth":26,"bounds":{"left":0.12450133,"top":0.39185953,"width":0.22589761,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"bounds":{"left":0.26163563,"top":0.41260973,"width":0.011968086,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"could result in an aggressive \"busy loop\" that spikes CPU usage.","depth":26,"bounds":{"left":0.12450133,"top":0.41101357,"width":0.22290559,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"4. Performance Overhead","depth":23,"bounds":{"left":0.111369684,"top":0.48004788,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4. Performance Overhead","depth":24,"bounds":{"left":0.111369684,"top":0.47964883,"width":0.076130316,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Expensive Garbage Collection:","depth":26,"bounds":{"left":0.12450133,"top":0.5067837,"width":0.078457445,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Calling","depth":26,"bounds":{"left":0.20295878,"top":0.5067837,"width":0.019281914,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles();","depth":27,"bounds":{"left":0.22423537,"top":0.5083799,"width":0.059840426,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"on","depth":26,"bounds":{"left":0.28607047,"top":0.5067837,"width":0.00880984,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"every single chunk","depth":26,"bounds":{"left":0.29488033,"top":0.5067837,"width":0.045545213,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.","depth":26,"bounds":{"left":0.12450133,"top":0.5067837,"width":0.23420878,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Synchronous Metric Dispatch:","depth":26,"bounds":{"left":0.12450133,"top":0.5738228,"width":0.07712766,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the","depth":26,"bounds":{"left":0.20162898,"top":0.5738228,"width":0.015292553,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Datadog::increment","depth":27,"bounds":{"left":0.21891622,"top":0.575419,"width":0.053856384,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"or","depth":26,"bounds":{"left":0.27476728,"top":0.5738228,"width":0.0078125,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Datadog::gauge","depth":27,"bounds":{"left":0.28457448,"top":0.575419,"width":0.041888297,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.","depth":26,"bounds":{"left":0.12450133,"top":0.5738228,"width":0.23420878,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"5. Lack of Explicit Error Handling","depth":23,"bounds":{"left":0.111369684,"top":0.66201115,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5. Lack of Explicit Error Handling","depth":24,"bounds":{"left":0.111369684,"top":0.66161215,"width":0.094913565,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No","depth":26,"bounds":{"left":0.12450133,"top":0.688747,"width":0.008477394,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"try/catch","depth":27,"bounds":{"left":0.1349734,"top":0.6903432,"width":0.026928192,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Blocks:","depth":26,"bounds":{"left":0.16389628,"top":0.688747,"width":0.019614361,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The script executes complex network operations (","depth":26,"bounds":{"left":0.18351063,"top":0.688747,"width":0.12367021,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
238207153713282688
|
-1079889075687295012
|
click
|
accessibility
|
NULL
|
JY-20676 delete AJ reports related objects by Laky JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
github.com
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage.
4. Performance Overhead
4. Performance Overhead
Expensive Garbage Collection:
Calling
gc_collect_cycles();
on
every single chunk
can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.
Synchronous Metric Dispatch:
If the
Datadog::increment
or
Datadog::gauge
methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.
5. Lack of Explicit Error Handling
5. Lack of Explicit Error Handling
No
try/catch
Blocks:
The script executes complex network operations (...
|
64674
|
NULL
|
NULL
|
NULL
|
|
64678
|
2265
|
55
|
2026-05-20T14:11:34.220983+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779286294220_m2.jpg...
|
Firefox
|
JY-20676 delete AJ reports related objects by Laky JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app — Work...
|
1
|
github.com/jiminny/app/pull/12098
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage....
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.10721409,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.15791224,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.1245012,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.1796875,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.12699468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":4,"bounds":{"left":0.0,"top":0.21548285,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":5,"bounds":{"left":0.013297873,"top":0.22665602,"width":0.10139628,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"bounds":{"left":0.0,"top":0.2482043,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.25937748,"width":0.18068483,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.28092578,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.29209897,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.31364724,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.32482043,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.3463687,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.3575419,"width":0.15824468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.38068634,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"bounds":{"left":0.35854387,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"bounds":{"left":0.37051198,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Main menu","depth":13,"bounds":{"left":0.08228058,"top":0.09736632,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New chat","depth":12,"bounds":{"left":0.09823803,"top":0.10215483,"width":0.028091755,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":15,"bounds":{"left":0.1008976,"top":0.10654429,"width":0.02144282,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Chat","depth":13,"bounds":{"left":0.35854387,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":13,"bounds":{"left":0.37051198,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"bounds":{"left":0.079288565,"top":0.14445332,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"bounds":{"left":0.079288565,"top":0.14684756,"width":0.1200133,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"You said I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a concise summary of the changes made in the code selection:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Refactoring and Dependencies","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Refactoring and Dependencies","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Replaced Interface with Implementation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusInterface","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusManager","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in the imports and constructor dependency injection.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Redis Support:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Imported","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Illuminate\\Support\\Facades\\Redis","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to handle a newly introduced memory-logging lock mechanism.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Traits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WorkerIdTrait","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", along with the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"worker","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"field tracking in the final logs.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Loop and Worker Lifecycle Adjustments","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Loop and Worker Lifecycle Adjustments","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Worker Auto-Restart:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Deleted the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property, the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"constant, and the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"shouldIterateAgain()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method. The script no longer tracks execution iterations or triggers a self-managed worker restart.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Simplified Chunk Method Return:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Modified","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to always return","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Priority Handling for Throttled Entities","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Priority Handling for Throttled Entities","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Stale Record Elevation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Updated the logic in","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"rescheduleThrottledEntities()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to check if a throttled entity group is classified as \"stale\". If it is, the entities are rescheduled using","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"QueuePriorityEnum::NORMAL","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"; otherwise, they keep their respective queue priority.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Metrics, Logging, and Environment Handling","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Metrics, Logging, and Environment Handling","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Environment Tracking:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Introduced a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$environment","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property (defaulting to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'local'","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") and a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"setEnvironment()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method to dynamically assign it.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Memory Monitoring via Datadog Gauge:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method that submits peak memory allocation to Datadog as a gauge metric (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"jiminny.reindex-memory-usage","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis Lock for Metrics Throttling:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Implemented a Redis-backed lock (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"%s-process-manager-lock","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") with a 60-second expiration inside","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to prevent over-reporting metrics.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Log Payload Update:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added total processed items (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$updatedDocuments + $deletedDocuments","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") to the final information log.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":23,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said are there any issues or potential bottlenecks","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"are there any issues or potential bottlenecks","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a breakdown of the potential issues to look out for:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"1. Memory Leak Risk (OOM Crashes)","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. Memory Leak Risk (OOM Crashes)","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Restart Logic:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The most significant change is the removal of the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Dependency Leaks:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If any underlying service (like the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ElasticSearchClient","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", ORM, or Datadog helper) retains references to processed entities,","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"bounds":{"left":0.26811835,"top":0.0,"width":0.056848403,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23138298,"height":0.05546688},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Hardcoded Limits:","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.047041222,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The code still includes","depth":26,"bounds":{"left":0.17154256,"top":0.0,"width":0.05651596,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ini_set('memory_limit', '512M');","depth":27,"bounds":{"left":0.23005319,"top":0.0,"width":0.095744684,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23055187,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"2. Race Condition in Redis Lock","depth":23,"bounds":{"left":0.111369684,"top":0.06823623,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. Race Condition in Redis Lock","depth":24,"bounds":{"left":0.111369684,"top":0.06783719,"width":0.09125665,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Non-Atomic Operations:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.062333778,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The","depth":26,"bounds":{"left":0.18683511,"top":0.09497207,"width":0.011635638,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage","depth":27,"bounds":{"left":0.20046543,"top":0.096568234,"width":0.041888297,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method uses three distinct steps to handle the lock:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.22722739,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"get()","depth":27,"bounds":{"left":0.14860372,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":26,"bounds":{"left":0.16555852,"top":0.11412609,"width":0.002493351,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.17004654,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", and","depth":26,"bounds":{"left":0.18700133,"top":0.11412609,"width":0.013131649,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"expire()","depth":27,"bounds":{"left":0.20212767,"top":0.11572227,"width":0.023936171,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.22805852,"top":0.11412609,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If multiple workers process the same","depth":26,"bounds":{"left":0.16456117,"top":0.14285715,"width":0.09275266,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$this->updateTarget","depth":27,"bounds":{"left":0.25930852,"top":0.14445332,"width":0.056848403,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"concurrently, they can both pass the","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"if (! is_null(Redis::get(...)))","depth":27,"bounds":{"left":0.18351063,"top":0.16360734,"width":0.09275266,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"check at the exact same millisecond. Both will then execute","depth":26,"bounds":{"left":0.12450133,"top":0.16201118,"width":0.21575798,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.2122673,"top":0.18276137,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and push duplicate metrics to Datadog.","depth":26,"bounds":{"left":0.22922207,"top":0.1811652,"width":0.09823803,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Fix:","depth":26,"bounds":{"left":0.12450133,"top":0.20989625,"width":0.019281914,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This should be replaced with a single atomic operation using Redis's","depth":26,"bounds":{"left":0.14378324,"top":0.20989625,"width":0.16821809,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"NX","depth":27,"bounds":{"left":0.31399602,"top":0.21149242,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Not eXists) and","depth":26,"bounds":{"left":0.12450133,"top":0.20989625,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"EX","depth":27,"bounds":{"left":0.13696809,"top":0.23064645,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Expire) flags, or Laravel’s built-in atomic cache locks:","depth":26,"bounds":{"left":0.14494681,"top":0.22905028,"width":0.13264628,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis::set($gaugeLockName, true, 'EX', 60, 'NX')","depth":27,"bounds":{"left":0.12649602,"top":0.24980047,"width":0.14361702,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.27210772,"top":0.2482043,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"3. Infinite Loops and Caller Contract","depth":23,"bounds":{"left":0.111369684,"top":0.2980846,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. Infinite Loops and Caller Contract","depth":24,"bounds":{"left":0.111369684,"top":0.29768556,"width":0.10571808,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Return Value Change:","depth":26,"bounds":{"left":0.12450133,"top":0.32482043,"width":0.055352394,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"bounds":{"left":0.18301196,"top":0.3264166,"width":0.06881649,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"previously returned whether it should iterate again based on staleness or chunk size. It now always returns","depth":26,"bounds":{"left":0.12450133,"top":0.32482043,"width":0.2237367,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"bounds":{"left":0.29438165,"top":0.34557062,"width":0.011968086,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(unless the selection is totally empty).","depth":26,"bounds":{"left":0.12450133,"top":0.34397447,"width":0.2130984,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.39185953,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the code calling this method relies on a","depth":26,"bounds":{"left":0.16456117,"top":0.39185953,"width":0.10305851,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"false","depth":27,"bounds":{"left":0.26961437,"top":0.3934557,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"return to pause, sleep, or gracefully terminate, changing this to an unconditional","depth":26,"bounds":{"left":0.12450133,"top":0.39185953,"width":0.22589761,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"bounds":{"left":0.26163563,"top":0.41260973,"width":0.011968086,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"could result in an aggressive \"busy loop\" that spikes CPU usage.","depth":26,"bounds":{"left":0.12450133,"top":0.41101357,"width":0.22290559,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
7056788041877329843
|
-1079889110047033380
|
visual_change
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage....
|
NULL
|
NULL
|
NULL
|
NULL
|
|
64679
|
2265
|
56
|
2026-05-20T14:11:37.269434+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779286297269_m2.jpg...
|
Firefox
|
JY-20676 delete AJ reports related objects by Laky JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app — Work...
|
1
|
github.com/jiminny/app/pull/12098
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage.
4. Performance Overhead
4. Performance Overhead
Expensive Garbage Collection:
Calling
gc_collect_cycles();
on
every single chunk
can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.
Synchronous Metric Dispatch:
If the
Datadog::increment
or
Datadog::gauge
methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.
5. Lack of Explicit Error Handling
5. Lack of Explicit Error Handling
No
try/catch
Blocks:
The script executes complex network operations (
loadDocuments
, Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.
Good response
Bad response
Redo
Share & export...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.10721409,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.15791224,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.1245012,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.1796875,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.12699468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":4,"bounds":{"left":0.0,"top":0.21548285,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":5,"bounds":{"left":0.013297873,"top":0.22665602,"width":0.10139628,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"bounds":{"left":0.0,"top":0.2482043,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.25937748,"width":0.18068483,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.28092578,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.29209897,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.31364724,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.32482043,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.3463687,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.3575419,"width":0.15824468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.38068634,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"bounds":{"left":0.35854387,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"bounds":{"left":0.37051198,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Main menu","depth":13,"bounds":{"left":0.08228058,"top":0.09736632,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New chat","depth":12,"bounds":{"left":0.09823803,"top":0.10215483,"width":0.028091755,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":15,"bounds":{"left":0.1008976,"top":0.10654429,"width":0.02144282,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Chat","depth":13,"bounds":{"left":0.35854387,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":13,"bounds":{"left":0.37051198,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"bounds":{"left":0.079288565,"top":0.14445332,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"bounds":{"left":0.079288565,"top":0.14684756,"width":0.1200133,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"You said I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a concise summary of the changes made in the code selection:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Refactoring and Dependencies","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Refactoring and Dependencies","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Replaced Interface with Implementation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusInterface","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusManager","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in the imports and constructor dependency injection.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Redis Support:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Imported","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Illuminate\\Support\\Facades\\Redis","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to handle a newly introduced memory-logging lock mechanism.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Traits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WorkerIdTrait","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", along with the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"worker","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"field tracking in the final logs.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Loop and Worker Lifecycle Adjustments","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Loop and Worker Lifecycle Adjustments","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Worker Auto-Restart:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Deleted the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property, the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"constant, and the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"shouldIterateAgain()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method. The script no longer tracks execution iterations or triggers a self-managed worker restart.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Simplified Chunk Method Return:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Modified","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to always return","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Priority Handling for Throttled Entities","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Priority Handling for Throttled Entities","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Stale Record Elevation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Updated the logic in","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"rescheduleThrottledEntities()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to check if a throttled entity group is classified as \"stale\". If it is, the entities are rescheduled using","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"QueuePriorityEnum::NORMAL","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"; otherwise, they keep their respective queue priority.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Metrics, Logging, and Environment Handling","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Metrics, Logging, and Environment Handling","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Environment Tracking:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Introduced a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$environment","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property (defaulting to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'local'","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") and a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"setEnvironment()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method to dynamically assign it.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Memory Monitoring via Datadog Gauge:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method that submits peak memory allocation to Datadog as a gauge metric (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"jiminny.reindex-memory-usage","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis Lock for Metrics Throttling:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Implemented a Redis-backed lock (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"%s-process-manager-lock","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") with a 60-second expiration inside","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to prevent over-reporting metrics.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Log Payload Update:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added total processed items (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$updatedDocuments + $deletedDocuments","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") to the final information log.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":23,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said are there any issues or potential bottlenecks","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"are there any issues or potential bottlenecks","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a breakdown of the potential issues to look out for:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"1. Memory Leak Risk (OOM Crashes)","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. Memory Leak Risk (OOM Crashes)","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Restart Logic:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The most significant change is the removal of the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Dependency Leaks:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If any underlying service (like the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ElasticSearchClient","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", ORM, or Datadog helper) retains references to processed entities,","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"bounds":{"left":0.26811835,"top":0.0,"width":0.056848403,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23138298,"height":0.05546688},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Hardcoded Limits:","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.047041222,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The code still includes","depth":26,"bounds":{"left":0.17154256,"top":0.0,"width":0.05651596,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ini_set('memory_limit', '512M');","depth":27,"bounds":{"left":0.23005319,"top":0.0,"width":0.095744684,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23055187,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"2. Race Condition in Redis Lock","depth":23,"bounds":{"left":0.111369684,"top":0.06823623,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. Race Condition in Redis Lock","depth":24,"bounds":{"left":0.111369684,"top":0.06783719,"width":0.09125665,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Non-Atomic Operations:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.062333778,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The","depth":26,"bounds":{"left":0.18683511,"top":0.09497207,"width":0.011635638,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage","depth":27,"bounds":{"left":0.20046543,"top":0.096568234,"width":0.041888297,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method uses three distinct steps to handle the lock:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.22722739,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"get()","depth":27,"bounds":{"left":0.14860372,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":26,"bounds":{"left":0.16555852,"top":0.11412609,"width":0.002493351,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.17004654,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", and","depth":26,"bounds":{"left":0.18700133,"top":0.11412609,"width":0.013131649,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"expire()","depth":27,"bounds":{"left":0.20212767,"top":0.11572227,"width":0.023936171,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.22805852,"top":0.11412609,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If multiple workers process the same","depth":26,"bounds":{"left":0.16456117,"top":0.14285715,"width":0.09275266,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$this->updateTarget","depth":27,"bounds":{"left":0.25930852,"top":0.14445332,"width":0.056848403,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"concurrently, they can both pass the","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"if (! is_null(Redis::get(...)))","depth":27,"bounds":{"left":0.18351063,"top":0.16360734,"width":0.09275266,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"check at the exact same millisecond. Both will then execute","depth":26,"bounds":{"left":0.12450133,"top":0.16201118,"width":0.21575798,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.2122673,"top":0.18276137,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and push duplicate metrics to Datadog.","depth":26,"bounds":{"left":0.22922207,"top":0.1811652,"width":0.09823803,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Fix:","depth":26,"bounds":{"left":0.12450133,"top":0.20989625,"width":0.019281914,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This should be replaced with a single atomic operation using Redis's","depth":26,"bounds":{"left":0.14378324,"top":0.20989625,"width":0.16821809,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"NX","depth":27,"bounds":{"left":0.31399602,"top":0.21149242,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Not eXists) and","depth":26,"bounds":{"left":0.12450133,"top":0.20989625,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"EX","depth":27,"bounds":{"left":0.13696809,"top":0.23064645,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Expire) flags, or Laravel’s built-in atomic cache locks:","depth":26,"bounds":{"left":0.14494681,"top":0.22905028,"width":0.13264628,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis::set($gaugeLockName, true, 'EX', 60, 'NX')","depth":27,"bounds":{"left":0.12649602,"top":0.24980047,"width":0.14361702,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.27210772,"top":0.2482043,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"3. Infinite Loops and Caller Contract","depth":23,"bounds":{"left":0.111369684,"top":0.2980846,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. Infinite Loops and Caller Contract","depth":24,"bounds":{"left":0.111369684,"top":0.29768556,"width":0.10571808,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Return Value Change:","depth":26,"bounds":{"left":0.12450133,"top":0.32482043,"width":0.055352394,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"bounds":{"left":0.18301196,"top":0.3264166,"width":0.06881649,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"previously returned whether it should iterate again based on staleness or chunk size. It now always returns","depth":26,"bounds":{"left":0.12450133,"top":0.32482043,"width":0.2237367,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"bounds":{"left":0.29438165,"top":0.34557062,"width":0.011968086,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(unless the selection is totally empty).","depth":26,"bounds":{"left":0.12450133,"top":0.34397447,"width":0.2130984,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.39185953,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the code calling this method relies on a","depth":26,"bounds":{"left":0.16456117,"top":0.39185953,"width":0.10305851,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"false","depth":27,"bounds":{"left":0.26961437,"top":0.3934557,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"return to pause, sleep, or gracefully terminate, changing this to an unconditional","depth":26,"bounds":{"left":0.12450133,"top":0.39185953,"width":0.22589761,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"bounds":{"left":0.26163563,"top":0.41260973,"width":0.011968086,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"could result in an aggressive \"busy loop\" that spikes CPU usage.","depth":26,"bounds":{"left":0.12450133,"top":0.41101357,"width":0.22290559,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"4. Performance Overhead","depth":23,"bounds":{"left":0.111369684,"top":0.48004788,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4. Performance Overhead","depth":24,"bounds":{"left":0.111369684,"top":0.47964883,"width":0.076130316,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Expensive Garbage Collection:","depth":26,"bounds":{"left":0.12450133,"top":0.5067837,"width":0.078457445,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Calling","depth":26,"bounds":{"left":0.20295878,"top":0.5067837,"width":0.019281914,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles();","depth":27,"bounds":{"left":0.22423537,"top":0.5083799,"width":0.059840426,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"on","depth":26,"bounds":{"left":0.28607047,"top":0.5067837,"width":0.00880984,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"every single chunk","depth":26,"bounds":{"left":0.29488033,"top":0.5067837,"width":0.045545213,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.","depth":26,"bounds":{"left":0.12450133,"top":0.5067837,"width":0.23420878,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Synchronous Metric Dispatch:","depth":26,"bounds":{"left":0.12450133,"top":0.5738228,"width":0.07712766,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the","depth":26,"bounds":{"left":0.20162898,"top":0.5738228,"width":0.015292553,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Datadog::increment","depth":27,"bounds":{"left":0.21891622,"top":0.575419,"width":0.053856384,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"or","depth":26,"bounds":{"left":0.27476728,"top":0.5738228,"width":0.0078125,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Datadog::gauge","depth":27,"bounds":{"left":0.28457448,"top":0.575419,"width":0.041888297,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.","depth":26,"bounds":{"left":0.12450133,"top":0.5738228,"width":0.23420878,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"5. Lack of Explicit Error Handling","depth":23,"bounds":{"left":0.111369684,"top":0.66201115,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5. Lack of Explicit Error Handling","depth":24,"bounds":{"left":0.111369684,"top":0.66161215,"width":0.094913565,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No","depth":26,"bounds":{"left":0.12450133,"top":0.688747,"width":0.008477394,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"try/catch","depth":27,"bounds":{"left":0.1349734,"top":0.6903432,"width":0.026928192,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Blocks:","depth":26,"bounds":{"left":0.16389628,"top":0.688747,"width":0.019614361,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The script executes complex network operations (","depth":26,"bounds":{"left":0.18351063,"top":0.688747,"width":0.12367021,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"loadDocuments","depth":27,"bounds":{"left":0.30917552,"top":0.6903432,"width":0.038896278,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.","depth":26,"bounds":{"left":0.12450133,"top":0.688747,"width":0.22888963,"height":0.09377494},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":22,"bounds":{"left":0.1087101,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":22,"bounds":{"left":0.124667555,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Redo","depth":22,"bounds":{"left":0.140625,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":21,"bounds":{"left":0.15658244,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
8963481789372004064
|
-1070952279536486436
|
visual_change
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage.
4. Performance Overhead
4. Performance Overhead
Expensive Garbage Collection:
Calling
gc_collect_cycles();
on
every single chunk
can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.
Synchronous Metric Dispatch:
If the
Datadog::increment
or
Datadog::gauge
methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.
5. Lack of Explicit Error Handling
5. Lack of Explicit Error Handling
No
try/catch
Blocks:
The script executes complex network operations (
loadDocuments
, Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.
Good response
Bad response
Redo
Share & export...
|
64678
|
NULL
|
NULL
|
NULL
|
|
64680
|
2265
|
57
|
2026-05-20T14:11:43.286996+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779286303286_m2.jpg...
|
Firefox
|
JY-20676 delete AJ reports related objects by Laky JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app — Work...
|
1
|
github.com/jiminny/app/pull/12098
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage.
4. Performance Overhead
4. Performance Overhead
Expensive Garbage Collection:
Calling
gc_collect_cycles();
on
every single chunk
can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.
Synchronous Metric Dispatch:
If the
Datadog::increment
or
Datadog::gauge
methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.
5. Lack of Explicit Error Handling
5. Lack of Explicit Error Handling
No
try/catch
Blocks:
The script executes complex network operations (
loadDocuments
, Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.
Good response
Bad response
Redo
Share & export
Copy
Show more options
Ask Gemini
Ask Gemini...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.10721409,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.15791224,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.1245012,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.1796875,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.12699468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":4,"bounds":{"left":0.0,"top":0.21548285,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":5,"bounds":{"left":0.013297873,"top":0.22665602,"width":0.10139628,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"bounds":{"left":0.0,"top":0.2482043,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.25937748,"width":0.18068483,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.28092578,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.29209897,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.31364724,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.32482043,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.3463687,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.3575419,"width":0.15824468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.38068634,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"bounds":{"left":0.35854387,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"bounds":{"left":0.37051198,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Main menu","depth":13,"bounds":{"left":0.08228058,"top":0.09736632,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New chat","depth":12,"bounds":{"left":0.09823803,"top":0.10215483,"width":0.028091755,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":15,"bounds":{"left":0.1008976,"top":0.10654429,"width":0.02144282,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Chat","depth":13,"bounds":{"left":0.35854387,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":13,"bounds":{"left":0.37051198,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"bounds":{"left":0.079288565,"top":0.14445332,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"bounds":{"left":0.079288565,"top":0.14684756,"width":0.1200133,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"You said I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a concise summary of the changes made in the code selection:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Refactoring and Dependencies","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Refactoring and Dependencies","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Replaced Interface with Implementation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusInterface","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusManager","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in the imports and constructor dependency injection.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Redis Support:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Imported","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Illuminate\\Support\\Facades\\Redis","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to handle a newly introduced memory-logging lock mechanism.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Traits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WorkerIdTrait","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", along with the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"worker","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"field tracking in the final logs.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Loop and Worker Lifecycle Adjustments","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Loop and Worker Lifecycle Adjustments","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Worker Auto-Restart:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Deleted the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property, the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"constant, and the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"shouldIterateAgain()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method. The script no longer tracks execution iterations or triggers a self-managed worker restart.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Simplified Chunk Method Return:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Modified","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to always return","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Priority Handling for Throttled Entities","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Priority Handling for Throttled Entities","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Stale Record Elevation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Updated the logic in","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"rescheduleThrottledEntities()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to check if a throttled entity group is classified as \"stale\". If it is, the entities are rescheduled using","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"QueuePriorityEnum::NORMAL","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"; otherwise, they keep their respective queue priority.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Metrics, Logging, and Environment Handling","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Metrics, Logging, and Environment Handling","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Environment Tracking:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Introduced a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$environment","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property (defaulting to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'local'","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") and a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"setEnvironment()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method to dynamically assign it.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Memory Monitoring via Datadog Gauge:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method that submits peak memory allocation to Datadog as a gauge metric (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"jiminny.reindex-memory-usage","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis Lock for Metrics Throttling:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Implemented a Redis-backed lock (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"%s-process-manager-lock","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") with a 60-second expiration inside","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to prevent over-reporting metrics.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Log Payload Update:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added total processed items (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$updatedDocuments + $deletedDocuments","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") to the final information log.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":23,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said are there any issues or potential bottlenecks","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"are there any issues or potential bottlenecks","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a breakdown of the potential issues to look out for:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"1. Memory Leak Risk (OOM Crashes)","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. Memory Leak Risk (OOM Crashes)","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Restart Logic:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The most significant change is the removal of the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Dependency Leaks:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If any underlying service (like the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ElasticSearchClient","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", ORM, or Datadog helper) retains references to processed entities,","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"bounds":{"left":0.26811835,"top":0.0,"width":0.056848403,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23138298,"height":0.05546688},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Hardcoded Limits:","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.047041222,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The code still includes","depth":26,"bounds":{"left":0.17154256,"top":0.0,"width":0.05651596,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ini_set('memory_limit', '512M');","depth":27,"bounds":{"left":0.23005319,"top":0.0,"width":0.095744684,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23055187,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"2. Race Condition in Redis Lock","depth":23,"bounds":{"left":0.111369684,"top":0.06823623,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. Race Condition in Redis Lock","depth":24,"bounds":{"left":0.111369684,"top":0.06783719,"width":0.09125665,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Non-Atomic Operations:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.062333778,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The","depth":26,"bounds":{"left":0.18683511,"top":0.09497207,"width":0.011635638,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage","depth":27,"bounds":{"left":0.20046543,"top":0.096568234,"width":0.041888297,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method uses three distinct steps to handle the lock:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.22722739,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"get()","depth":27,"bounds":{"left":0.14860372,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":26,"bounds":{"left":0.16555852,"top":0.11412609,"width":0.002493351,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.17004654,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", and","depth":26,"bounds":{"left":0.18700133,"top":0.11412609,"width":0.013131649,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"expire()","depth":27,"bounds":{"left":0.20212767,"top":0.11572227,"width":0.023936171,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.22805852,"top":0.11412609,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If multiple workers process the same","depth":26,"bounds":{"left":0.16456117,"top":0.14285715,"width":0.09275266,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$this->updateTarget","depth":27,"bounds":{"left":0.25930852,"top":0.14445332,"width":0.056848403,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"concurrently, they can both pass the","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"if (! is_null(Redis::get(...)))","depth":27,"bounds":{"left":0.18351063,"top":0.16360734,"width":0.09275266,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"check at the exact same millisecond. Both will then execute","depth":26,"bounds":{"left":0.12450133,"top":0.16201118,"width":0.21575798,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.2122673,"top":0.18276137,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and push duplicate metrics to Datadog.","depth":26,"bounds":{"left":0.22922207,"top":0.1811652,"width":0.09823803,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Fix:","depth":26,"bounds":{"left":0.12450133,"top":0.20989625,"width":0.019281914,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This should be replaced with a single atomic operation using Redis's","depth":26,"bounds":{"left":0.14378324,"top":0.20989625,"width":0.16821809,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"NX","depth":27,"bounds":{"left":0.31399602,"top":0.21149242,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Not eXists) and","depth":26,"bounds":{"left":0.12450133,"top":0.20989625,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"EX","depth":27,"bounds":{"left":0.13696809,"top":0.23064645,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Expire) flags, or Laravel’s built-in atomic cache locks:","depth":26,"bounds":{"left":0.14494681,"top":0.22905028,"width":0.13264628,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis::set($gaugeLockName, true, 'EX', 60, 'NX')","depth":27,"bounds":{"left":0.12649602,"top":0.24980047,"width":0.14361702,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.27210772,"top":0.2482043,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"3. Infinite Loops and Caller Contract","depth":23,"bounds":{"left":0.111369684,"top":0.2980846,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. Infinite Loops and Caller Contract","depth":24,"bounds":{"left":0.111369684,"top":0.29768556,"width":0.10571808,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Return Value Change:","depth":26,"bounds":{"left":0.12450133,"top":0.32482043,"width":0.055352394,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"bounds":{"left":0.18301196,"top":0.3264166,"width":0.06881649,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"previously returned whether it should iterate again based on staleness or chunk size. It now always returns","depth":26,"bounds":{"left":0.12450133,"top":0.32482043,"width":0.2237367,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"bounds":{"left":0.29438165,"top":0.34557062,"width":0.011968086,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(unless the selection is totally empty).","depth":26,"bounds":{"left":0.12450133,"top":0.34397447,"width":0.2130984,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.39185953,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the code calling this method relies on a","depth":26,"bounds":{"left":0.16456117,"top":0.39185953,"width":0.10305851,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"false","depth":27,"bounds":{"left":0.26961437,"top":0.3934557,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"return to pause, sleep, or gracefully terminate, changing this to an unconditional","depth":26,"bounds":{"left":0.12450133,"top":0.39185953,"width":0.22589761,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"bounds":{"left":0.26163563,"top":0.41260973,"width":0.011968086,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"could result in an aggressive \"busy loop\" that spikes CPU usage.","depth":26,"bounds":{"left":0.12450133,"top":0.41101357,"width":0.22290559,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"4. Performance Overhead","depth":23,"bounds":{"left":0.111369684,"top":0.48004788,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4. Performance Overhead","depth":24,"bounds":{"left":0.111369684,"top":0.47964883,"width":0.076130316,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Expensive Garbage Collection:","depth":26,"bounds":{"left":0.12450133,"top":0.5067837,"width":0.078457445,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Calling","depth":26,"bounds":{"left":0.20295878,"top":0.5067837,"width":0.019281914,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles();","depth":27,"bounds":{"left":0.22423537,"top":0.5083799,"width":0.059840426,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"on","depth":26,"bounds":{"left":0.28607047,"top":0.5067837,"width":0.00880984,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"every single chunk","depth":26,"bounds":{"left":0.29488033,"top":0.5067837,"width":0.045545213,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.","depth":26,"bounds":{"left":0.12450133,"top":0.5067837,"width":0.23420878,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Synchronous Metric Dispatch:","depth":26,"bounds":{"left":0.12450133,"top":0.5738228,"width":0.07712766,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the","depth":26,"bounds":{"left":0.20162898,"top":0.5738228,"width":0.015292553,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Datadog::increment","depth":27,"bounds":{"left":0.21891622,"top":0.575419,"width":0.053856384,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"or","depth":26,"bounds":{"left":0.27476728,"top":0.5738228,"width":0.0078125,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Datadog::gauge","depth":27,"bounds":{"left":0.28457448,"top":0.575419,"width":0.041888297,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.","depth":26,"bounds":{"left":0.12450133,"top":0.5738228,"width":0.23420878,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"5. Lack of Explicit Error Handling","depth":23,"bounds":{"left":0.111369684,"top":0.66201115,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5. Lack of Explicit Error Handling","depth":24,"bounds":{"left":0.111369684,"top":0.66161215,"width":0.094913565,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No","depth":26,"bounds":{"left":0.12450133,"top":0.688747,"width":0.008477394,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"try/catch","depth":27,"bounds":{"left":0.1349734,"top":0.6903432,"width":0.026928192,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Blocks:","depth":26,"bounds":{"left":0.16389628,"top":0.688747,"width":0.019614361,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The script executes complex network operations (","depth":26,"bounds":{"left":0.18351063,"top":0.688747,"width":0.12367021,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"loadDocuments","depth":27,"bounds":{"left":0.30917552,"top":0.6903432,"width":0.038896278,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.","depth":26,"bounds":{"left":0.12450133,"top":0.688747,"width":0.22888963,"height":0.09377494},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":22,"bounds":{"left":0.1087101,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":22,"bounds":{"left":0.124667555,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Redo","depth":22,"bounds":{"left":0.140625,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":21,"bounds":{"left":0.15658244,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":22,"bounds":{"left":0.17253989,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":21,"bounds":{"left":0.18849733,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXTextArea","text":"Ask Gemini","depth":21,"bounds":{"left":0.14594415,"top":0.86632085,"width":0.17021276,"height":0.01915403},"on_screen":true,"value":"Ask Gemini","help_text":"","role_description":"text entry area","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Ask Gemini","depth":22,"bounds":{"left":0.14594415,"top":0.8667199,"width":0.030418882,"height":0.018355945},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
1062163830408800351
|
-1070952279540680932
|
visual_change
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage.
4. Performance Overhead
4. Performance Overhead
Expensive Garbage Collection:
Calling
gc_collect_cycles();
on
every single chunk
can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.
Synchronous Metric Dispatch:
If the
Datadog::increment
or
Datadog::gauge
methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.
5. Lack of Explicit Error Handling
5. Lack of Explicit Error Handling
No
try/catch
Blocks:
The script executes complex network operations (
loadDocuments
, Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.
Good response
Bad response
Redo
Share & export
Copy
Show more options
Ask Gemini
Ask Gemini...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
64681
|
2265
|
58
|
2026-05-20T14:11:46.304657+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779286306304_m2.jpg...
|
Firefox
|
JY-20676 delete AJ reports related objects by Laky JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app — Work...
|
1
|
github.com/jiminny/app/pull/12098
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.10721409,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.15791224,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.1245012,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.1796875,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.12699468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":4,"bounds":{"left":0.0,"top":0.21548285,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":5,"bounds":{"left":0.013297873,"top":0.22665602,"width":0.10139628,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"bounds":{"left":0.0,"top":0.2482043,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.25937748,"width":0.18068483,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.28092578,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.29209897,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.31364724,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.32482043,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.3463687,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.3575419,"width":0.15824468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.38068634,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"bounds":{"left":0.35854387,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"bounds":{"left":0.37051198,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Main menu","depth":13,"bounds":{"left":0.08228058,"top":0.09736632,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New chat","depth":12,"bounds":{"left":0.09823803,"top":0.10215483,"width":0.028091755,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":15,"bounds":{"left":0.1008976,"top":0.10654429,"width":0.02144282,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Chat","depth":13,"bounds":{"left":0.35854387,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":13,"bounds":{"left":0.37051198,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"bounds":{"left":0.079288565,"top":0.14445332,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"bounds":{"left":0.079288565,"top":0.14684756,"width":0.1200133,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"You said I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a concise summary of the changes made in the code selection:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Refactoring and Dependencies","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Refactoring and Dependencies","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Replaced Interface with Implementation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusInterface","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusManager","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in the imports and constructor dependency injection.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Redis Support:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Imported","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Illuminate\\Support\\Facades\\Redis","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to handle a newly introduced memory-logging lock mechanism.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Traits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WorkerIdTrait","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", along with the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"worker","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"field tracking in the final logs.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Loop and Worker Lifecycle Adjustments","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Loop and Worker Lifecycle Adjustments","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Worker Auto-Restart:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Deleted the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property, the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"constant, and the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"shouldIterateAgain()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method. The script no longer tracks execution iterations or triggers a self-managed worker restart.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Simplified Chunk Method Return:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Modified","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to always return","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Priority Handling for Throttled Entities","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Priority Handling for Throttled Entities","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Stale Record Elevation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Updated the logic in","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"rescheduleThrottledEntities()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to check if a throttled entity group is classified as \"stale\". If it is, the entities are rescheduled using","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"QueuePriorityEnum::NORMAL","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"; otherwise, they keep their respective queue priority.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Metrics, Logging, and Environment Handling","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Metrics, Logging, and Environment Handling","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Environment Tracking:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Introduced a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$environment","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property (defaulting to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'local'","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") and a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"setEnvironment()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method to dynamically assign it.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Memory Monitoring via Datadog Gauge:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method that submits peak memory allocation to Datadog as a gauge metric (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"jiminny.reindex-memory-usage","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis Lock for Metrics Throttling:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Implemented a Redis-backed lock (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"%s-process-manager-lock","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") with a 60-second expiration inside","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to prevent over-reporting metrics.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Log Payload Update:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added total processed items (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$updatedDocuments + $deletedDocuments","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") to the final information log.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":23,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said are there any issues or potential bottlenecks","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"are there any issues or potential bottlenecks","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a breakdown of the potential issues to look out for:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"1. Memory Leak Risk (OOM Crashes)","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. Memory Leak Risk (OOM Crashes)","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Restart Logic:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The most significant change is the removal of the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Dependency Leaks:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If any underlying service (like the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ElasticSearchClient","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", ORM, or Datadog helper) retains references to processed entities,","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"bounds":{"left":0.26811835,"top":0.0,"width":0.056848403,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23138298,"height":0.05546688},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Hardcoded Limits:","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.047041222,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The code still includes","depth":26,"bounds":{"left":0.17154256,"top":0.0,"width":0.05651596,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ini_set('memory_limit', '512M');","depth":27,"bounds":{"left":0.23005319,"top":0.0,"width":0.095744684,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23055187,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"2. Race Condition in Redis Lock","depth":23,"bounds":{"left":0.111369684,"top":0.06823623,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. Race Condition in Redis Lock","depth":24,"bounds":{"left":0.111369684,"top":0.06783719,"width":0.09125665,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Non-Atomic Operations:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.062333778,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The","depth":26,"bounds":{"left":0.18683511,"top":0.09497207,"width":0.011635638,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage","depth":27,"bounds":{"left":0.20046543,"top":0.096568234,"width":0.041888297,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method uses three distinct steps to handle the lock:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.22722739,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"get()","depth":27,"bounds":{"left":0.14860372,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":26,"bounds":{"left":0.16555852,"top":0.11412609,"width":0.002493351,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.17004654,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", and","depth":26,"bounds":{"left":0.18700133,"top":0.11412609,"width":0.013131649,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"expire()","depth":27,"bounds":{"left":0.20212767,"top":0.11572227,"width":0.023936171,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.22805852,"top":0.11412609,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If multiple workers process the same","depth":26,"bounds":{"left":0.16456117,"top":0.14285715,"width":0.09275266,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
1526423920121232508
|
-1061874711537551460
|
visual_change
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same...
|
64680
|
NULL
|
NULL
|
NULL
|
|
64682
|
2265
|
59
|
2026-05-20T14:11:49.335587+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779286309335_m2.jpg...
|
Firefox
|
JY-20676 delete AJ reports related objects by Laky JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app — Work...
|
1
|
github.com/jiminny/app/pull/12098
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.10721409,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.15791224,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.1245012,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.1796875,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.12699468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":4,"bounds":{"left":0.0,"top":0.21548285,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":5,"bounds":{"left":0.013297873,"top":0.22665602,"width":0.10139628,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"bounds":{"left":0.0,"top":0.2482043,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.25937748,"width":0.18068483,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.28092578,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.29209897,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.31364724,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.32482043,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.3463687,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.3575419,"width":0.15824468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.38068634,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"bounds":{"left":0.35854387,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"bounds":{"left":0.37051198,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Main menu","depth":13,"bounds":{"left":0.08228058,"top":0.09736632,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New chat","depth":12,"bounds":{"left":0.09823803,"top":0.10215483,"width":0.028091755,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":15,"bounds":{"left":0.1008976,"top":0.10654429,"width":0.02144282,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Chat","depth":13,"bounds":{"left":0.35854387,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":13,"bounds":{"left":0.37051198,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"bounds":{"left":0.079288565,"top":0.14445332,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"bounds":{"left":0.079288565,"top":0.14684756,"width":0.1200133,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"You said I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a concise summary of the changes made in the code selection:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Refactoring and Dependencies","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Refactoring and Dependencies","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Replaced Interface with Implementation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusInterface","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusManager","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in the imports and constructor dependency injection.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Redis Support:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Imported","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Illuminate\\Support\\Facades\\Redis","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to handle a newly introduced memory-logging lock mechanism.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Traits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WorkerIdTrait","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", along with the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"worker","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"field tracking in the final logs.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Loop and Worker Lifecycle Adjustments","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Loop and Worker Lifecycle Adjustments","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Worker Auto-Restart:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Deleted the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property, the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"constant, and the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"shouldIterateAgain()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method. The script no longer tracks execution iterations or triggers a self-managed worker restart.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Simplified Chunk Method Return:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Modified","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to always return","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Priority Handling for Throttled Entities","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Priority Handling for Throttled Entities","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Stale Record Elevation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Updated the logic in","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"rescheduleThrottledEntities()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to check if a throttled entity group is classified as \"stale\". If it is, the entities are rescheduled using","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"QueuePriorityEnum::NORMAL","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"; otherwise, they keep their respective queue priority.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Metrics, Logging, and Environment Handling","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Metrics, Logging, and Environment Handling","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Environment Tracking:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Introduced a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$environment","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property (defaulting to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'local'","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") and a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"setEnvironment()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method to dynamically assign it.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Memory Monitoring via Datadog Gauge:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method that submits peak memory allocation to Datadog as a gauge metric (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"jiminny.reindex-memory-usage","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis Lock for Metrics Throttling:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Implemented a Redis-backed lock (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"%s-process-manager-lock","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") with a 60-second expiration inside","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to prevent over-reporting metrics.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Log Payload Update:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added total processed items (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$updatedDocuments + $deletedDocuments","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") to the final information log.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":23,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said are there any issues or potential bottlenecks","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"are there any issues or potential bottlenecks","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a breakdown of the potential issues to look out for:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"1. Memory Leak Risk (OOM Crashes)","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. Memory Leak Risk (OOM Crashes)","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Restart Logic:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The most significant change is the removal of the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Dependency Leaks:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If any underlying service (like the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ElasticSearchClient","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", ORM, or Datadog helper) retains references to processed entities,","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"bounds":{"left":0.26811835,"top":0.0,"width":0.056848403,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23138298,"height":0.05546688},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Hardcoded Limits:","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.047041222,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The code still includes","depth":26,"bounds":{"left":0.17154256,"top":0.0,"width":0.05651596,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ini_set('memory_limit', '512M');","depth":27,"bounds":{"left":0.23005319,"top":0.0,"width":0.095744684,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23055187,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"2. Race Condition in Redis Lock","depth":23,"bounds":{"left":0.111369684,"top":0.06823623,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. Race Condition in Redis Lock","depth":24,"bounds":{"left":0.111369684,"top":0.06783719,"width":0.09125665,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Non-Atomic Operations:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.062333778,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The","depth":26,"bounds":{"left":0.18683511,"top":0.09497207,"width":0.011635638,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage","depth":27,"bounds":{"left":0.20046543,"top":0.096568234,"width":0.041888297,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method uses three distinct steps to handle the lock:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.22722739,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"get()","depth":27,"bounds":{"left":0.14860372,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":26,"bounds":{"left":0.16555852,"top":0.11412609,"width":0.002493351,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.17004654,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", and","depth":26,"bounds":{"left":0.18700133,"top":0.11412609,"width":0.013131649,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"expire()","depth":27,"bounds":{"left":0.20212767,"top":0.11572227,"width":0.023936171,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.22805852,"top":0.11412609,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If multiple workers process the same","depth":26,"bounds":{"left":0.16456117,"top":0.14285715,"width":0.09275266,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$this->updateTarget","depth":27,"bounds":{"left":0.25930852,"top":0.14445332,"width":0.056848403,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"concurrently, they can both pass the","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"if (! is_null(Redis::get(...)))","depth":27,"bounds":{"left":0.18351063,"top":0.16360734,"width":0.09275266,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"check at the exact same millisecond. Both will then execute","depth":26,"bounds":{"left":0.12450133,"top":0.16201118,"width":0.21575798,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.2122673,"top":0.18276137,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and push duplicate metrics to Datadog.","depth":26,"bounds":{"left":0.22922207,"top":0.1811652,"width":0.09823803,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Fix:","depth":26,"bounds":{"left":0.12450133,"top":0.20989625,"width":0.019281914,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This should be replaced with a single atomic operation using Redis's","depth":26,"bounds":{"left":0.14378324,"top":0.20989625,"width":0.16821809,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"NX","depth":27,"bounds":{"left":0.31399602,"top":0.21149242,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Not eXists) and","depth":26,"bounds":{"left":0.12450133,"top":0.20989625,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"EX","depth":27,"bounds":{"left":0.13696809,"top":0.23064645,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Expire) flags, or Laravel’s built-in atomic cache locks:","depth":26,"bounds":{"left":0.14494681,"top":0.22905028,"width":0.13264628,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis::set($gaugeLockName, true, 'EX', 60, 'NX')","depth":27,"bounds":{"left":0.12649602,"top":0.24980047,"width":0.14361702,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.27210772,"top":0.2482043,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"3. Infinite Loops and Caller Contract","depth":23,"bounds":{"left":0.111369684,"top":0.2980846,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. Infinite Loops and Caller Contract","depth":24,"bounds":{"left":0.111369684,"top":0.29768556,"width":0.10571808,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Return Value Change:","depth":26,"bounds":{"left":0.12450133,"top":0.32482043,"width":0.055352394,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"bounds":{"left":0.18301196,"top":0.3264166,"width":0.06881649,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-7347303376054114365
|
-1061874711537616932
|
visual_change
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
64683
|
2265
|
60
|
2026-05-20T14:11:52.321423+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779286312321_m2.jpg...
|
Firefox
|
JY-20676 delete AJ reports related objects by Laky JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app — Work...
|
1
|
github.com/jiminny/app/pull/12098
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.10721409,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.15791224,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.1245012,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.1796875,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.12699468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":4,"bounds":{"left":0.0,"top":0.21548285,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":5,"bounds":{"left":0.013297873,"top":0.22665602,"width":0.10139628,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"bounds":{"left":0.0,"top":0.2482043,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.25937748,"width":0.18068483,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.28092578,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.29209897,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.31364724,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.32482043,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.3463687,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.3575419,"width":0.15824468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.38068634,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"bounds":{"left":0.35854387,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"bounds":{"left":0.37051198,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Main menu","depth":13,"bounds":{"left":0.08228058,"top":0.09736632,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New chat","depth":12,"bounds":{"left":0.09823803,"top":0.10215483,"width":0.028091755,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":15,"bounds":{"left":0.1008976,"top":0.10654429,"width":0.02144282,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Chat","depth":13,"bounds":{"left":0.35854387,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":13,"bounds":{"left":0.37051198,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"bounds":{"left":0.079288565,"top":0.14445332,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"bounds":{"left":0.079288565,"top":0.14684756,"width":0.1200133,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"You said I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a concise summary of the changes made in the code selection:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Refactoring and Dependencies","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Refactoring and Dependencies","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Replaced Interface with Implementation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusInterface","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusManager","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in the imports and constructor dependency injection.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Redis Support:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Imported","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Illuminate\\Support\\Facades\\Redis","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to handle a newly introduced memory-logging lock mechanism.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Traits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WorkerIdTrait","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", along with the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"worker","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"field tracking in the final logs.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Loop and Worker Lifecycle Adjustments","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Loop and Worker Lifecycle Adjustments","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Worker Auto-Restart:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Deleted the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property, the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"constant, and the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"shouldIterateAgain()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method. The script no longer tracks execution iterations or triggers a self-managed worker restart.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Simplified Chunk Method Return:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Modified","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to always return","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Priority Handling for Throttled Entities","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Priority Handling for Throttled Entities","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Stale Record Elevation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Updated the logic in","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"rescheduleThrottledEntities()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to check if a throttled entity group is classified as \"stale\". If it is, the entities are rescheduled using","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"QueuePriorityEnum::NORMAL","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"; otherwise, they keep their respective queue priority.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Metrics, Logging, and Environment Handling","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Metrics, Logging, and Environment Handling","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Environment Tracking:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Introduced a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$environment","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property (defaulting to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'local'","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") and a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"setEnvironment()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method to dynamically assign it.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Memory Monitoring via Datadog Gauge:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method that submits peak memory allocation to Datadog as a gauge metric (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"jiminny.reindex-memory-usage","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis Lock for Metrics Throttling:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Implemented a Redis-backed lock (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"%s-process-manager-lock","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") with a 60-second expiration inside","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to prevent over-reporting metrics.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Log Payload Update:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added total processed items (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$updatedDocuments + $deletedDocuments","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") to the final information log.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":23,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said are there any issues or potential bottlenecks","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"are there any issues or potential bottlenecks","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a breakdown of the potential issues to look out for:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"1. Memory Leak Risk (OOM Crashes)","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. Memory Leak Risk (OOM Crashes)","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Restart Logic:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The most significant change is the removal of the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Dependency Leaks:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If any underlying service (like the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ElasticSearchClient","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", ORM, or Datadog helper) retains references to processed entities,","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"bounds":{"left":0.26811835,"top":0.0,"width":0.056848403,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23138298,"height":0.05546688},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Hardcoded Limits:","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.047041222,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The code still includes","depth":26,"bounds":{"left":0.17154256,"top":0.0,"width":0.05651596,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ini_set('memory_limit', '512M');","depth":27,"bounds":{"left":0.23005319,"top":0.0,"width":0.095744684,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23055187,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"2. Race Condition in Redis Lock","depth":23,"bounds":{"left":0.111369684,"top":0.06823623,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. Race Condition in Redis Lock","depth":24,"bounds":{"left":0.111369684,"top":0.06783719,"width":0.09125665,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Non-Atomic Operations:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.062333778,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The","depth":26,"bounds":{"left":0.18683511,"top":0.09497207,"width":0.011635638,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage","depth":27,"bounds":{"left":0.20046543,"top":0.096568234,"width":0.041888297,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method uses three distinct steps to handle the lock:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.22722739,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"get()","depth":27,"bounds":{"left":0.14860372,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":26,"bounds":{"left":0.16555852,"top":0.11412609,"width":0.002493351,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.17004654,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", and","depth":26,"bounds":{"left":0.18700133,"top":0.11412609,"width":0.013131649,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"expire()","depth":27,"bounds":{"left":0.20212767,"top":0.11572227,"width":0.023936171,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.22805852,"top":0.11412609,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If multiple workers process the same","depth":26,"bounds":{"left":0.16456117,"top":0.14285715,"width":0.09275266,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$this->updateTarget","depth":27,"bounds":{"left":0.25930852,"top":0.14445332,"width":0.056848403,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"concurrently, they can both pass the","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"if (! is_null(Redis::get(...)))","depth":27,"bounds":{"left":0.18351063,"top":0.16360734,"width":0.09275266,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"check at the exact same millisecond. Both will then execute","depth":26,"bounds":{"left":0.12450133,"top":0.16201118,"width":0.21575798,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.2122673,"top":0.18276137,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-8621523292286619302
|
-1061874711537616932
|
visual_change
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()...
|
64682
|
NULL
|
NULL
|
NULL
|
|
64684
|
2265
|
61
|
2026-05-20T14:11:55.344293+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779286315344_m2.jpg...
|
Firefox
|
JY-20676 delete AJ reports related objects by Laky JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app — Work...
|
1
|
github.com/jiminny/app/pull/12098
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage.
4. Performance Overhead
4. Performance Overhead
Expensive Garbage Collection:
Calling
gc_collect_cycles();
on
every single chunk
can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.
Synchronous Metric Dispatch:
If the
Datadog::increment
or...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.10721409,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.15791224,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.1245012,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.1796875,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.12699468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":4,"bounds":{"left":0.0,"top":0.21548285,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":5,"bounds":{"left":0.013297873,"top":0.22665602,"width":0.10139628,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"bounds":{"left":0.0,"top":0.2482043,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.25937748,"width":0.18068483,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.28092578,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.29209897,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.31364724,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.32482043,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.3463687,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.3575419,"width":0.15824468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.38068634,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"bounds":{"left":0.35854387,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"bounds":{"left":0.37051198,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Main menu","depth":13,"bounds":{"left":0.08228058,"top":0.09736632,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New chat","depth":12,"bounds":{"left":0.09823803,"top":0.10215483,"width":0.028091755,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":15,"bounds":{"left":0.1008976,"top":0.10654429,"width":0.02144282,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Chat","depth":13,"bounds":{"left":0.35854387,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":13,"bounds":{"left":0.37051198,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"bounds":{"left":0.079288565,"top":0.14445332,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"bounds":{"left":0.079288565,"top":0.14684756,"width":0.1200133,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"You said I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a concise summary of the changes made in the code selection:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Refactoring and Dependencies","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Refactoring and Dependencies","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Replaced Interface with Implementation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusInterface","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusManager","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in the imports and constructor dependency injection.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Redis Support:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Imported","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Illuminate\\Support\\Facades\\Redis","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to handle a newly introduced memory-logging lock mechanism.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Traits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WorkerIdTrait","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", along with the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"worker","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"field tracking in the final logs.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Loop and Worker Lifecycle Adjustments","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Loop and Worker Lifecycle Adjustments","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Worker Auto-Restart:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Deleted the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property, the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"constant, and the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"shouldIterateAgain()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method. The script no longer tracks execution iterations or triggers a self-managed worker restart.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Simplified Chunk Method Return:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Modified","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to always return","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Priority Handling for Throttled Entities","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Priority Handling for Throttled Entities","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Stale Record Elevation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Updated the logic in","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"rescheduleThrottledEntities()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to check if a throttled entity group is classified as \"stale\". If it is, the entities are rescheduled using","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"QueuePriorityEnum::NORMAL","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"; otherwise, they keep their respective queue priority.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Metrics, Logging, and Environment Handling","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Metrics, Logging, and Environment Handling","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Environment Tracking:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Introduced a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$environment","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property (defaulting to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'local'","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") and a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"setEnvironment()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method to dynamically assign it.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Memory Monitoring via Datadog Gauge:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method that submits peak memory allocation to Datadog as a gauge metric (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"jiminny.reindex-memory-usage","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis Lock for Metrics Throttling:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Implemented a Redis-backed lock (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"%s-process-manager-lock","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") with a 60-second expiration inside","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to prevent over-reporting metrics.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Log Payload Update:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added total processed items (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$updatedDocuments + $deletedDocuments","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") to the final information log.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":23,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said are there any issues or potential bottlenecks","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"are there any issues or potential bottlenecks","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a breakdown of the potential issues to look out for:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"1. Memory Leak Risk (OOM Crashes)","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. Memory Leak Risk (OOM Crashes)","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Restart Logic:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The most significant change is the removal of the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Dependency Leaks:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If any underlying service (like the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ElasticSearchClient","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", ORM, or Datadog helper) retains references to processed entities,","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"bounds":{"left":0.26811835,"top":0.0,"width":0.056848403,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23138298,"height":0.05546688},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Hardcoded Limits:","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.047041222,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The code still includes","depth":26,"bounds":{"left":0.17154256,"top":0.0,"width":0.05651596,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ini_set('memory_limit', '512M');","depth":27,"bounds":{"left":0.23005319,"top":0.0,"width":0.095744684,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23055187,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"2. Race Condition in Redis Lock","depth":23,"bounds":{"left":0.111369684,"top":0.06823623,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. Race Condition in Redis Lock","depth":24,"bounds":{"left":0.111369684,"top":0.06783719,"width":0.09125665,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Non-Atomic Operations:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.062333778,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The","depth":26,"bounds":{"left":0.18683511,"top":0.09497207,"width":0.011635638,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage","depth":27,"bounds":{"left":0.20046543,"top":0.096568234,"width":0.041888297,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method uses three distinct steps to handle the lock:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.22722739,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"get()","depth":27,"bounds":{"left":0.14860372,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":26,"bounds":{"left":0.16555852,"top":0.11412609,"width":0.002493351,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.17004654,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", and","depth":26,"bounds":{"left":0.18700133,"top":0.11412609,"width":0.013131649,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"expire()","depth":27,"bounds":{"left":0.20212767,"top":0.11572227,"width":0.023936171,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.22805852,"top":0.11412609,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If multiple workers process the same","depth":26,"bounds":{"left":0.16456117,"top":0.14285715,"width":0.09275266,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$this->updateTarget","depth":27,"bounds":{"left":0.25930852,"top":0.14445332,"width":0.056848403,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"concurrently, they can both pass the","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"if (! is_null(Redis::get(...)))","depth":27,"bounds":{"left":0.18351063,"top":0.16360734,"width":0.09275266,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"check at the exact same millisecond. Both will then execute","depth":26,"bounds":{"left":0.12450133,"top":0.16201118,"width":0.21575798,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.2122673,"top":0.18276137,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and push duplicate metrics to Datadog.","depth":26,"bounds":{"left":0.22922207,"top":0.1811652,"width":0.09823803,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Fix:","depth":26,"bounds":{"left":0.12450133,"top":0.20989625,"width":0.019281914,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This should be replaced with a single atomic operation using Redis's","depth":26,"bounds":{"left":0.14378324,"top":0.20989625,"width":0.16821809,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"NX","depth":27,"bounds":{"left":0.31399602,"top":0.21149242,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Not eXists) and","depth":26,"bounds":{"left":0.12450133,"top":0.20989625,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"EX","depth":27,"bounds":{"left":0.13696809,"top":0.23064645,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Expire) flags, or Laravel’s built-in atomic cache locks:","depth":26,"bounds":{"left":0.14494681,"top":0.22905028,"width":0.13264628,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis::set($gaugeLockName, true, 'EX', 60, 'NX')","depth":27,"bounds":{"left":0.12649602,"top":0.24980047,"width":0.14361702,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.27210772,"top":0.2482043,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"3. Infinite Loops and Caller Contract","depth":23,"bounds":{"left":0.111369684,"top":0.2980846,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. Infinite Loops and Caller Contract","depth":24,"bounds":{"left":0.111369684,"top":0.29768556,"width":0.10571808,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Return Value Change:","depth":26,"bounds":{"left":0.12450133,"top":0.32482043,"width":0.055352394,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"bounds":{"left":0.18301196,"top":0.3264166,"width":0.06881649,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"previously returned whether it should iterate again based on staleness or chunk size. It now always returns","depth":26,"bounds":{"left":0.12450133,"top":0.32482043,"width":0.2237367,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"bounds":{"left":0.29438165,"top":0.34557062,"width":0.011968086,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(unless the selection is totally empty).","depth":26,"bounds":{"left":0.12450133,"top":0.34397447,"width":0.2130984,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.39185953,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the code calling this method relies on a","depth":26,"bounds":{"left":0.16456117,"top":0.39185953,"width":0.10305851,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"false","depth":27,"bounds":{"left":0.26961437,"top":0.3934557,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"return to pause, sleep, or gracefully terminate, changing this to an unconditional","depth":26,"bounds":{"left":0.12450133,"top":0.39185953,"width":0.22589761,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"bounds":{"left":0.26163563,"top":0.41260973,"width":0.011968086,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"could result in an aggressive \"busy loop\" that spikes CPU usage.","depth":26,"bounds":{"left":0.12450133,"top":0.41101357,"width":0.22290559,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"4. Performance Overhead","depth":23,"bounds":{"left":0.111369684,"top":0.48004788,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4. Performance Overhead","depth":24,"bounds":{"left":0.111369684,"top":0.47964883,"width":0.076130316,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Expensive Garbage Collection:","depth":26,"bounds":{"left":0.12450133,"top":0.5067837,"width":0.078457445,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Calling","depth":26,"bounds":{"left":0.20295878,"top":0.5067837,"width":0.019281914,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles();","depth":27,"bounds":{"left":0.22423537,"top":0.5083799,"width":0.059840426,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"on","depth":26,"bounds":{"left":0.28607047,"top":0.5067837,"width":0.00880984,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"every single chunk","depth":26,"bounds":{"left":0.29488033,"top":0.5067837,"width":0.045545213,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.","depth":26,"bounds":{"left":0.12450133,"top":0.5067837,"width":0.23420878,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Synchronous Metric Dispatch:","depth":26,"bounds":{"left":0.12450133,"top":0.5738228,"width":0.07712766,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the","depth":26,"bounds":{"left":0.20162898,"top":0.5738228,"width":0.015292553,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Datadog::increment","depth":27,"bounds":{"left":0.21891622,"top":0.575419,"width":0.053856384,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"or","depth":26,"bounds":{"left":0.27476728,"top":0.5738228,"width":0.0078125,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-412904074452865308
|
-1079959478791211044
|
visual_change
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage.
4. Performance Overhead
4. Performance Overhead
Expensive Garbage Collection:
Calling
gc_collect_cycles();
on
every single chunk
can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.
Synchronous Metric Dispatch:
If the
Datadog::increment
or...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
64685
|
2265
|
62
|
2026-05-20T14:11:58.374328+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779286318374_m2.jpg...
|
Firefox
|
JY-20676 delete AJ reports related objects by Laky JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app — Work...
|
1
|
github.com/jiminny/app/pull/12098
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.10721409,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.15791224,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.1245012,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.1796875,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.12699468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":4,"bounds":{"left":0.0,"top":0.21548285,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":5,"bounds":{"left":0.013297873,"top":0.22665602,"width":0.10139628,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"bounds":{"left":0.0,"top":0.2482043,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.25937748,"width":0.18068483,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.28092578,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.29209897,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.31364724,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.32482043,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.3463687,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.3575419,"width":0.15824468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.38068634,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"bounds":{"left":0.35854387,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"bounds":{"left":0.37051198,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Main menu","depth":13,"bounds":{"left":0.08228058,"top":0.09736632,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New chat","depth":12,"bounds":{"left":0.09823803,"top":0.10215483,"width":0.028091755,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":15,"bounds":{"left":0.1008976,"top":0.10654429,"width":0.02144282,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Chat","depth":13,"bounds":{"left":0.35854387,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":13,"bounds":{"left":0.37051198,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"bounds":{"left":0.079288565,"top":0.14445332,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"bounds":{"left":0.079288565,"top":0.14684756,"width":0.1200133,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"You said I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a concise summary of the changes made in the code selection:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Refactoring and Dependencies","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Refactoring and Dependencies","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Replaced Interface with Implementation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusInterface","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusManager","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in the imports and constructor dependency injection.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Redis Support:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Imported","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Illuminate\\Support\\Facades\\Redis","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to handle a newly introduced memory-logging lock mechanism.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Traits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WorkerIdTrait","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", along with the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"worker","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"field tracking in the final logs.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Loop and Worker Lifecycle Adjustments","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Loop and Worker Lifecycle Adjustments","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Worker Auto-Restart:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Deleted the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property, the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"constant, and the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"shouldIterateAgain()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method. The script no longer tracks execution iterations or triggers a self-managed worker restart.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Simplified Chunk Method Return:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Modified","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to always return","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Priority Handling for Throttled Entities","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Priority Handling for Throttled Entities","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Stale Record Elevation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Updated the logic in","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"rescheduleThrottledEntities()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to check if a throttled entity group is classified as \"stale\". If it is, the entities are rescheduled using","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"QueuePriorityEnum::NORMAL","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"; otherwise, they keep their respective queue priority.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Metrics, Logging, and Environment Handling","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Metrics, Logging, and Environment Handling","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Environment Tracking:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Introduced a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$environment","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property (defaulting to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'local'","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") and a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"setEnvironment()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method to dynamically assign it.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Memory Monitoring via Datadog Gauge:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method that submits peak memory allocation to Datadog as a gauge metric (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"jiminny.reindex-memory-usage","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis Lock for Metrics Throttling:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Implemented a Redis-backed lock (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"%s-process-manager-lock","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") with a 60-second expiration inside","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to prevent over-reporting metrics.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Log Payload Update:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added total processed items (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$updatedDocuments + $deletedDocuments","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") to the final information log.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
8150348838347613474
|
-1061874681472845924
|
visual_change
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response...
|
64684
|
NULL
|
NULL
|
NULL
|
|
64686
|
2265
|
63
|
2026-05-20T14:12:01.389410+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779286321389_m2.jpg...
|
Firefox
|
JY-20676 delete AJ reports related objects by Laky JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app — Work...
|
1
|
github.com/jiminny/app/pull/12098
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage.
4. Performance Overhead
4. Performance Overhead...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.10721409,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.15791224,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.1245012,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.1796875,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.12699468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":4,"bounds":{"left":0.0,"top":0.21548285,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":5,"bounds":{"left":0.013297873,"top":0.22665602,"width":0.10139628,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"bounds":{"left":0.0,"top":0.2482043,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.25937748,"width":0.18068483,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.28092578,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.29209897,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.31364724,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.32482043,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.3463687,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.3575419,"width":0.15824468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.38068634,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"bounds":{"left":0.35854387,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"bounds":{"left":0.37051198,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Main menu","depth":13,"bounds":{"left":0.08228058,"top":0.09736632,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New chat","depth":12,"bounds":{"left":0.09823803,"top":0.10215483,"width":0.028091755,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":15,"bounds":{"left":0.1008976,"top":0.10654429,"width":0.02144282,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Chat","depth":13,"bounds":{"left":0.35854387,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":13,"bounds":{"left":0.37051198,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"bounds":{"left":0.079288565,"top":0.14445332,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"bounds":{"left":0.079288565,"top":0.14684756,"width":0.1200133,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"You said I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a concise summary of the changes made in the code selection:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Refactoring and Dependencies","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Refactoring and Dependencies","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Replaced Interface with Implementation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusInterface","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusManager","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in the imports and constructor dependency injection.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Redis Support:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Imported","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Illuminate\\Support\\Facades\\Redis","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to handle a newly introduced memory-logging lock mechanism.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Traits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WorkerIdTrait","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", along with the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"worker","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"field tracking in the final logs.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Loop and Worker Lifecycle Adjustments","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Loop and Worker Lifecycle Adjustments","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Worker Auto-Restart:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Deleted the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property, the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"constant, and the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"shouldIterateAgain()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method. The script no longer tracks execution iterations or triggers a self-managed worker restart.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Simplified Chunk Method Return:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Modified","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to always return","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Priority Handling for Throttled Entities","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Priority Handling for Throttled Entities","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Stale Record Elevation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Updated the logic in","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"rescheduleThrottledEntities()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to check if a throttled entity group is classified as \"stale\". If it is, the entities are rescheduled using","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"QueuePriorityEnum::NORMAL","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"; otherwise, they keep their respective queue priority.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Metrics, Logging, and Environment Handling","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Metrics, Logging, and Environment Handling","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Environment Tracking:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Introduced a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$environment","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property (defaulting to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'local'","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") and a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"setEnvironment()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method to dynamically assign it.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Memory Monitoring via Datadog Gauge:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method that submits peak memory allocation to Datadog as a gauge metric (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"jiminny.reindex-memory-usage","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis Lock for Metrics Throttling:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Implemented a Redis-backed lock (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"%s-process-manager-lock","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") with a 60-second expiration inside","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to prevent over-reporting metrics.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Log Payload Update:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added total processed items (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$updatedDocuments + $deletedDocuments","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") to the final information log.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":23,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said are there any issues or potential bottlenecks","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"are there any issues or potential bottlenecks","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a breakdown of the potential issues to look out for:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"1. Memory Leak Risk (OOM Crashes)","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. Memory Leak Risk (OOM Crashes)","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Restart Logic:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The most significant change is the removal of the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Dependency Leaks:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If any underlying service (like the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ElasticSearchClient","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", ORM, or Datadog helper) retains references to processed entities,","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"bounds":{"left":0.26811835,"top":0.0,"width":0.056848403,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23138298,"height":0.05546688},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Hardcoded Limits:","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.047041222,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The code still includes","depth":26,"bounds":{"left":0.17154256,"top":0.0,"width":0.05651596,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ini_set('memory_limit', '512M');","depth":27,"bounds":{"left":0.23005319,"top":0.0,"width":0.095744684,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23055187,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"2. Race Condition in Redis Lock","depth":23,"bounds":{"left":0.111369684,"top":0.06823623,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. Race Condition in Redis Lock","depth":24,"bounds":{"left":0.111369684,"top":0.06783719,"width":0.09125665,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Non-Atomic Operations:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.062333778,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The","depth":26,"bounds":{"left":0.18683511,"top":0.09497207,"width":0.011635638,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage","depth":27,"bounds":{"left":0.20046543,"top":0.096568234,"width":0.041888297,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method uses three distinct steps to handle the lock:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.22722739,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"get()","depth":27,"bounds":{"left":0.14860372,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":26,"bounds":{"left":0.16555852,"top":0.11412609,"width":0.002493351,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.17004654,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", and","depth":26,"bounds":{"left":0.18700133,"top":0.11412609,"width":0.013131649,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"expire()","depth":27,"bounds":{"left":0.20212767,"top":0.11572227,"width":0.023936171,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.22805852,"top":0.11412609,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If multiple workers process the same","depth":26,"bounds":{"left":0.16456117,"top":0.14285715,"width":0.09275266,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$this->updateTarget","depth":27,"bounds":{"left":0.25930852,"top":0.14445332,"width":0.056848403,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"concurrently, they can both pass the","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"if (! is_null(Redis::get(...)))","depth":27,"bounds":{"left":0.18351063,"top":0.16360734,"width":0.09275266,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"check at the exact same millisecond. Both will then execute","depth":26,"bounds":{"left":0.12450133,"top":0.16201118,"width":0.21575798,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.2122673,"top":0.18276137,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and push duplicate metrics to Datadog.","depth":26,"bounds":{"left":0.22922207,"top":0.1811652,"width":0.09823803,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Fix:","depth":26,"bounds":{"left":0.12450133,"top":0.20989625,"width":0.019281914,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This should be replaced with a single atomic operation using Redis's","depth":26,"bounds":{"left":0.14378324,"top":0.20989625,"width":0.16821809,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"NX","depth":27,"bounds":{"left":0.31399602,"top":0.21149242,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Not eXists) and","depth":26,"bounds":{"left":0.12450133,"top":0.20989625,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"EX","depth":27,"bounds":{"left":0.13696809,"top":0.23064645,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Expire) flags, or Laravel’s built-in atomic cache locks:","depth":26,"bounds":{"left":0.14494681,"top":0.22905028,"width":0.13264628,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis::set($gaugeLockName, true, 'EX', 60, 'NX')","depth":27,"bounds":{"left":0.12649602,"top":0.24980047,"width":0.14361702,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.27210772,"top":0.2482043,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"3. Infinite Loops and Caller Contract","depth":23,"bounds":{"left":0.111369684,"top":0.2980846,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. Infinite Loops and Caller Contract","depth":24,"bounds":{"left":0.111369684,"top":0.29768556,"width":0.10571808,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Return Value Change:","depth":26,"bounds":{"left":0.12450133,"top":0.32482043,"width":0.055352394,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"bounds":{"left":0.18301196,"top":0.3264166,"width":0.06881649,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"previously returned whether it should iterate again based on staleness or chunk size. It now always returns","depth":26,"bounds":{"left":0.12450133,"top":0.32482043,"width":0.2237367,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"bounds":{"left":0.29438165,"top":0.34557062,"width":0.011968086,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(unless the selection is totally empty).","depth":26,"bounds":{"left":0.12450133,"top":0.34397447,"width":0.2130984,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.39185953,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the code calling this method relies on a","depth":26,"bounds":{"left":0.16456117,"top":0.39185953,"width":0.10305851,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"false","depth":27,"bounds":{"left":0.26961437,"top":0.3934557,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"return to pause, sleep, or gracefully terminate, changing this to an unconditional","depth":26,"bounds":{"left":0.12450133,"top":0.39185953,"width":0.22589761,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"bounds":{"left":0.26163563,"top":0.41260973,"width":0.011968086,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"could result in an aggressive \"busy loop\" that spikes CPU usage.","depth":26,"bounds":{"left":0.12450133,"top":0.41101357,"width":0.22290559,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"4. Performance Overhead","depth":23,"bounds":{"left":0.111369684,"top":0.48004788,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4. Performance Overhead","depth":24,"bounds":{"left":0.111369684,"top":0.47964883,"width":0.076130316,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
4566640402469731839
|
-1079889110047033380
|
visual_change
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage.
4. Performance Overhead
4. Performance Overhead...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
64687
|
2264
|
28
|
2026-05-20T14:12:03.762450+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779286323762_m1.jpg...
|
Firefox
|
JY-20676 delete AJ reports related objects by Laky JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app — Work...
|
1
|
github.com/jiminny/app/pull/12098
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Main menu","depth":13,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New chat","depth":12,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Chat","depth":13,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":13,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"You said I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a concise summary of the changes made in the code selection:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Refactoring and Dependencies","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Refactoring and Dependencies","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Replaced Interface with Implementation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusInterface","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusManager","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in the imports and constructor dependency injection.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Redis Support:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Imported","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Illuminate\\Support\\Facades\\Redis","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to handle a newly introduced memory-logging lock mechanism.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Traits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WorkerIdTrait","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", along with the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"worker","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"field tracking in the final logs.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Loop and Worker Lifecycle Adjustments","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Loop and Worker Lifecycle Adjustments","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Worker Auto-Restart:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Deleted the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property, the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"constant, and the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"shouldIterateAgain()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method. The script no longer tracks execution iterations or triggers a self-managed worker restart.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Simplified Chunk Method Return:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Modified","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to always return","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Priority Handling for Throttled Entities","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Priority Handling for Throttled Entities","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Stale Record Elevation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Updated the logic in","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"rescheduleThrottledEntities()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to check if a throttled entity group is classified as \"stale\". If it is, the entities are rescheduled using","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"QueuePriorityEnum::NORMAL","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"; otherwise, they keep their respective queue priority.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Metrics, Logging, and Environment Handling","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Metrics, Logging, and Environment Handling","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Environment Tracking:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Introduced a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$environment","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property (defaulting to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'local'","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") and a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"setEnvironment()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method to dynamically assign it.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Memory Monitoring via Datadog Gauge:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method that submits peak memory allocation to Datadog as a gauge metric (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"jiminny.reindex-memory-usage","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis Lock for Metrics Throttling:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Implemented a Redis-backed lock (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"%s-process-manager-lock","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") with a 60-second expiration inside","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to prevent over-reporting metrics.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Log Payload Update:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added total processed items (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$updatedDocuments + $deletedDocuments","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") to the final information log.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":23,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said are there any issues or potential bottlenecks","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"are there any issues or potential bottlenecks","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a breakdown of the potential issues to look out for:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"1. Memory Leak Risk (OOM Crashes)","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. Memory Leak Risk (OOM Crashes)","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Restart Logic:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The most significant change is the removal of the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Dependency Leaks:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If any underlying service (like the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ElasticSearchClient","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", ORM, or Datadog helper) retains references to processed entities,","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Hardcoded Limits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The code still includes","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ini_set('memory_limit', '512M');","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"2. Race Condition in Redis Lock","depth":23,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. Race Condition in Redis Lock","depth":24,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Non-Atomic Operations:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method uses three distinct steps to handle the lock:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"get()","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", and","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"expire()","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If multiple workers process the same","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$this->updateTarget","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"concurrently, they can both pass the","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"if (! is_null(Redis::get(...)))","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"check at the exact same millisecond. Both will then execute","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and push duplicate metrics to Datadog.","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Fix:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This should be replaced with a single atomic operation using Redis's","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"NX","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Not eXists) and","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"EX","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Expire) flags, or Laravel’s built-in atomic cache locks:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis::set($gaugeLockName, true, 'EX', 60, 'NX')","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"3. Infinite Loops and Caller Contract","depth":23,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. Infinite Loops and Caller Contract","depth":24,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Return Value Change:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-5042768006313656563
|
-1061874711537616932
|
idle
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
64688
|
2265
|
64
|
2026-05-20T14:12:04.466848+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779286324466_m2.jpg...
|
Firefox
|
JY-20676 delete AJ reports related objects by Laky JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app — Work...
|
1
|
github.com/jiminny/app/pull/12098
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.10721409,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.15791224,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.1245012,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.1796875,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.12699468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":4,"bounds":{"left":0.0,"top":0.21548285,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":5,"bounds":{"left":0.013297873,"top":0.22665602,"width":0.10139628,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"bounds":{"left":0.0,"top":0.2482043,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.25937748,"width":0.18068483,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.28092578,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.29209897,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.31364724,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.32482043,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.3463687,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.3575419,"width":0.15824468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.38068634,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"bounds":{"left":0.35854387,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"bounds":{"left":0.37051198,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Main menu","depth":13,"bounds":{"left":0.08228058,"top":0.09736632,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New chat","depth":12,"bounds":{"left":0.09823803,"top":0.10215483,"width":0.028091755,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":15,"bounds":{"left":0.1008976,"top":0.10654429,"width":0.02144282,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Chat","depth":13,"bounds":{"left":0.35854387,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":13,"bounds":{"left":0.37051198,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"bounds":{"left":0.079288565,"top":0.14445332,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"bounds":{"left":0.079288565,"top":0.14684756,"width":0.1200133,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"You said I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a concise summary of the changes made in the code selection:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Refactoring and Dependencies","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Refactoring and Dependencies","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Replaced Interface with Implementation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusInterface","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusManager","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in the imports and constructor dependency injection.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Redis Support:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Imported","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Illuminate\\Support\\Facades\\Redis","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to handle a newly introduced memory-logging lock mechanism.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Traits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WorkerIdTrait","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", along with the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"worker","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"field tracking in the final logs.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Loop and Worker Lifecycle Adjustments","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Loop and Worker Lifecycle Adjustments","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Worker Auto-Restart:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Deleted the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property, the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"constant, and the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"shouldIterateAgain()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method. The script no longer tracks execution iterations or triggers a self-managed worker restart.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Simplified Chunk Method Return:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Modified","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to always return","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Priority Handling for Throttled Entities","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Priority Handling for Throttled Entities","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Stale Record Elevation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Updated the logic in","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"rescheduleThrottledEntities()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to check if a throttled entity group is classified as \"stale\". If it is, the entities are rescheduled using","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"QueuePriorityEnum::NORMAL","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"; otherwise, they keep their respective queue priority.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Metrics, Logging, and Environment Handling","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Metrics, Logging, and Environment Handling","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Environment Tracking:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Introduced a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$environment","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property (defaulting to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'local'","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") and a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"setEnvironment()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method to dynamically assign it.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Memory Monitoring via Datadog Gauge:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method that submits peak memory allocation to Datadog as a gauge metric (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"jiminny.reindex-memory-usage","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis Lock for Metrics Throttling:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Implemented a Redis-backed lock (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"%s-process-manager-lock","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") with a 60-second expiration inside","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to prevent over-reporting metrics.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Log Payload Update:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added total processed items (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$updatedDocuments + $deletedDocuments","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") to the final information log.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":23,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said are there any issues or potential bottlenecks","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"are there any issues or potential bottlenecks","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a breakdown of the potential issues to look out for:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"1. Memory Leak Risk (OOM Crashes)","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. Memory Leak Risk (OOM Crashes)","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Restart Logic:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The most significant change is the removal of the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Dependency Leaks:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If any underlying service (like the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ElasticSearchClient","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", ORM, or Datadog helper) retains references to processed entities,","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"bounds":{"left":0.26811835,"top":0.0,"width":0.056848403,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23138298,"height":0.05546688},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Hardcoded Limits:","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.047041222,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The code still includes","depth":26,"bounds":{"left":0.17154256,"top":0.0,"width":0.05651596,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ini_set('memory_limit', '512M');","depth":27,"bounds":{"left":0.23005319,"top":0.0,"width":0.095744684,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23055187,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"2. Race Condition in Redis Lock","depth":23,"bounds":{"left":0.111369684,"top":0.06823623,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. Race Condition in Redis Lock","depth":24,"bounds":{"left":0.111369684,"top":0.06783719,"width":0.09125665,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-4682791562070316493
|
-1061874711537551460
|
visual_change
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock...
|
64686
|
NULL
|
NULL
|
NULL
|
|
64689
|
2265
|
65
|
2026-05-20T14:12:07.482162+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779286327482_m2.jpg...
|
Firefox
|
JY-20676 delete AJ reports related objects by Laky JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app — Work...
|
1
|
github.com/jiminny/app/pull/12098
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage.
4. Performance Overhead
4. Performance Overhead
Expensive Garbage Collection:
Calling
gc_collect_cycles();
on
every single chunk
can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.
Synchronous Metric Dispatch:
If the
Datadog::increment
or
Datadog::gauge
methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.
5. Lack of Explicit Error Handling
5. Lack of Explicit Error Handling
No
try/catch
Blocks:
The script executes complex network operations (
loadDocuments
, Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.
Good response
Bad response
Redo
Share & export
Copy
Show more options
Ask Gemini
Ask Gemini
Upload & tools
Microphone
Send message...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.10721409,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.15791224,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.1245012,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.1796875,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.12699468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":4,"bounds":{"left":0.0,"top":0.21548285,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":5,"bounds":{"left":0.013297873,"top":0.22665602,"width":0.10139628,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"bounds":{"left":0.0,"top":0.2482043,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.25937748,"width":0.18068483,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.28092578,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.29209897,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.31364724,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.32482043,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.3463687,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.3575419,"width":0.15824468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.38068634,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"bounds":{"left":0.35854387,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"bounds":{"left":0.37051198,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Main menu","depth":13,"bounds":{"left":0.08228058,"top":0.09736632,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New chat","depth":12,"bounds":{"left":0.09823803,"top":0.10215483,"width":0.028091755,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":15,"bounds":{"left":0.1008976,"top":0.10654429,"width":0.02144282,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Chat","depth":13,"bounds":{"left":0.35854387,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":13,"bounds":{"left":0.37051198,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"bounds":{"left":0.079288565,"top":0.14445332,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"bounds":{"left":0.079288565,"top":0.14684756,"width":0.1200133,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"You said I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a concise summary of the changes made in the code selection:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Refactoring and Dependencies","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Refactoring and Dependencies","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Replaced Interface with Implementation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusInterface","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusManager","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in the imports and constructor dependency injection.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Redis Support:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Imported","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Illuminate\\Support\\Facades\\Redis","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to handle a newly introduced memory-logging lock mechanism.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Traits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WorkerIdTrait","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", along with the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"worker","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"field tracking in the final logs.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Loop and Worker Lifecycle Adjustments","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Loop and Worker Lifecycle Adjustments","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Worker Auto-Restart:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Deleted the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property, the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"constant, and the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"shouldIterateAgain()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method. The script no longer tracks execution iterations or triggers a self-managed worker restart.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Simplified Chunk Method Return:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Modified","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to always return","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Priority Handling for Throttled Entities","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Priority Handling for Throttled Entities","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Stale Record Elevation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Updated the logic in","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"rescheduleThrottledEntities()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to check if a throttled entity group is classified as \"stale\". If it is, the entities are rescheduled using","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"QueuePriorityEnum::NORMAL","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"; otherwise, they keep their respective queue priority.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Metrics, Logging, and Environment Handling","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Metrics, Logging, and Environment Handling","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Environment Tracking:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Introduced a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$environment","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property (defaulting to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'local'","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") and a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"setEnvironment()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method to dynamically assign it.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Memory Monitoring via Datadog Gauge:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method that submits peak memory allocation to Datadog as a gauge metric (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"jiminny.reindex-memory-usage","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis Lock for Metrics Throttling:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Implemented a Redis-backed lock (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"%s-process-manager-lock","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") with a 60-second expiration inside","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to prevent over-reporting metrics.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Log Payload Update:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added total processed items (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$updatedDocuments + $deletedDocuments","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") to the final information log.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":23,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said are there any issues or potential bottlenecks","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"are there any issues or potential bottlenecks","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a breakdown of the potential issues to look out for:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"1. Memory Leak Risk (OOM Crashes)","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. Memory Leak Risk (OOM Crashes)","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Restart Logic:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The most significant change is the removal of the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Dependency Leaks:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If any underlying service (like the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ElasticSearchClient","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", ORM, or Datadog helper) retains references to processed entities,","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"bounds":{"left":0.26811835,"top":0.0,"width":0.056848403,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23138298,"height":0.05546688},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Hardcoded Limits:","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.047041222,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The code still includes","depth":26,"bounds":{"left":0.17154256,"top":0.0,"width":0.05651596,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ini_set('memory_limit', '512M');","depth":27,"bounds":{"left":0.23005319,"top":0.0,"width":0.095744684,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23055187,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"2. Race Condition in Redis Lock","depth":23,"bounds":{"left":0.111369684,"top":0.06823623,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. Race Condition in Redis Lock","depth":24,"bounds":{"left":0.111369684,"top":0.06783719,"width":0.09125665,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Non-Atomic Operations:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.062333778,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The","depth":26,"bounds":{"left":0.18683511,"top":0.09497207,"width":0.011635638,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage","depth":27,"bounds":{"left":0.20046543,"top":0.096568234,"width":0.041888297,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method uses three distinct steps to handle the lock:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.22722739,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"get()","depth":27,"bounds":{"left":0.14860372,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":26,"bounds":{"left":0.16555852,"top":0.11412609,"width":0.002493351,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.17004654,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", and","depth":26,"bounds":{"left":0.18700133,"top":0.11412609,"width":0.013131649,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"expire()","depth":27,"bounds":{"left":0.20212767,"top":0.11572227,"width":0.023936171,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.22805852,"top":0.11412609,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If multiple workers process the same","depth":26,"bounds":{"left":0.16456117,"top":0.14285715,"width":0.09275266,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$this->updateTarget","depth":27,"bounds":{"left":0.25930852,"top":0.14445332,"width":0.056848403,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"concurrently, they can both pass the","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"if (! is_null(Redis::get(...)))","depth":27,"bounds":{"left":0.18351063,"top":0.16360734,"width":0.09275266,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"check at the exact same millisecond. Both will then execute","depth":26,"bounds":{"left":0.12450133,"top":0.16201118,"width":0.21575798,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.2122673,"top":0.18276137,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and push duplicate metrics to Datadog.","depth":26,"bounds":{"left":0.22922207,"top":0.1811652,"width":0.09823803,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Fix:","depth":26,"bounds":{"left":0.12450133,"top":0.20989625,"width":0.019281914,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This should be replaced with a single atomic operation using Redis's","depth":26,"bounds":{"left":0.14378324,"top":0.20989625,"width":0.16821809,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"NX","depth":27,"bounds":{"left":0.31399602,"top":0.21149242,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Not eXists) and","depth":26,"bounds":{"left":0.12450133,"top":0.20989625,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"EX","depth":27,"bounds":{"left":0.13696809,"top":0.23064645,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Expire) flags, or Laravel’s built-in atomic cache locks:","depth":26,"bounds":{"left":0.14494681,"top":0.22905028,"width":0.13264628,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis::set($gaugeLockName, true, 'EX', 60, 'NX')","depth":27,"bounds":{"left":0.12649602,"top":0.24980047,"width":0.14361702,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.27210772,"top":0.2482043,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"3. Infinite Loops and Caller Contract","depth":23,"bounds":{"left":0.111369684,"top":0.2980846,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. Infinite Loops and Caller Contract","depth":24,"bounds":{"left":0.111369684,"top":0.29768556,"width":0.10571808,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Return Value Change:","depth":26,"bounds":{"left":0.12450133,"top":0.32482043,"width":0.055352394,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"bounds":{"left":0.18301196,"top":0.3264166,"width":0.06881649,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"previously returned whether it should iterate again based on staleness or chunk size. It now always returns","depth":26,"bounds":{"left":0.12450133,"top":0.32482043,"width":0.2237367,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"bounds":{"left":0.29438165,"top":0.34557062,"width":0.011968086,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(unless the selection is totally empty).","depth":26,"bounds":{"left":0.12450133,"top":0.34397447,"width":0.2130984,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.39185953,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the code calling this method relies on a","depth":26,"bounds":{"left":0.16456117,"top":0.39185953,"width":0.10305851,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"false","depth":27,"bounds":{"left":0.26961437,"top":0.3934557,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"return to pause, sleep, or gracefully terminate, changing this to an unconditional","depth":26,"bounds":{"left":0.12450133,"top":0.39185953,"width":0.22589761,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"bounds":{"left":0.26163563,"top":0.41260973,"width":0.011968086,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"could result in an aggressive \"busy loop\" that spikes CPU usage.","depth":26,"bounds":{"left":0.12450133,"top":0.41101357,"width":0.22290559,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"4. Performance Overhead","depth":23,"bounds":{"left":0.111369684,"top":0.48004788,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4. Performance Overhead","depth":24,"bounds":{"left":0.111369684,"top":0.47964883,"width":0.076130316,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Expensive Garbage Collection:","depth":26,"bounds":{"left":0.12450133,"top":0.5067837,"width":0.078457445,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Calling","depth":26,"bounds":{"left":0.20295878,"top":0.5067837,"width":0.019281914,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles();","depth":27,"bounds":{"left":0.22423537,"top":0.5083799,"width":0.059840426,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"on","depth":26,"bounds":{"left":0.28607047,"top":0.5067837,"width":0.00880984,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"every single chunk","depth":26,"bounds":{"left":0.29488033,"top":0.5067837,"width":0.045545213,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.","depth":26,"bounds":{"left":0.12450133,"top":0.5067837,"width":0.23420878,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Synchronous Metric Dispatch:","depth":26,"bounds":{"left":0.12450133,"top":0.5738228,"width":0.07712766,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the","depth":26,"bounds":{"left":0.20162898,"top":0.5738228,"width":0.015292553,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Datadog::increment","depth":27,"bounds":{"left":0.21891622,"top":0.575419,"width":0.053856384,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"or","depth":26,"bounds":{"left":0.27476728,"top":0.5738228,"width":0.0078125,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Datadog::gauge","depth":27,"bounds":{"left":0.28457448,"top":0.575419,"width":0.041888297,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.","depth":26,"bounds":{"left":0.12450133,"top":0.5738228,"width":0.23420878,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"5. Lack of Explicit Error Handling","depth":23,"bounds":{"left":0.111369684,"top":0.66201115,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5. Lack of Explicit Error Handling","depth":24,"bounds":{"left":0.111369684,"top":0.66161215,"width":0.094913565,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No","depth":26,"bounds":{"left":0.12450133,"top":0.688747,"width":0.008477394,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"try/catch","depth":27,"bounds":{"left":0.1349734,"top":0.6903432,"width":0.026928192,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Blocks:","depth":26,"bounds":{"left":0.16389628,"top":0.688747,"width":0.019614361,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The script executes complex network operations (","depth":26,"bounds":{"left":0.18351063,"top":0.688747,"width":0.12367021,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"loadDocuments","depth":27,"bounds":{"left":0.30917552,"top":0.6903432,"width":0.038896278,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.","depth":26,"bounds":{"left":0.12450133,"top":0.688747,"width":0.22888963,"height":0.09377494},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":22,"bounds":{"left":0.1087101,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":22,"bounds":{"left":0.124667555,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Redo","depth":22,"bounds":{"left":0.140625,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":21,"bounds":{"left":0.15658244,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":22,"bounds":{"left":0.17253989,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":21,"bounds":{"left":0.18849733,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXTextArea","text":"Ask Gemini","depth":21,"bounds":{"left":0.14594415,"top":0.86632085,"width":0.17021276,"height":0.01915403},"on_screen":true,"value":"Ask Gemini","help_text":"","role_description":"text entry area","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Ask Gemini","depth":22,"bounds":{"left":0.14594415,"top":0.8667199,"width":0.030418882,"height":0.018355945},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Upload & tools","depth":20,"bounds":{"left":0.12732713,"top":0.8567438,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Microphone","depth":20,"bounds":{"left":0.32413563,"top":0.86153233,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Send message","depth":20,"bounds":{"left":0.33211437,"top":0.8631285,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false}]...
|
-4892789105667151914
|
-1052937846671460580
|
visual_change
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage.
4. Performance Overhead
4. Performance Overhead
Expensive Garbage Collection:
Calling
gc_collect_cycles();
on
every single chunk
can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.
Synchronous Metric Dispatch:
If the
Datadog::increment
or
Datadog::gauge
methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.
5. Lack of Explicit Error Handling
5. Lack of Explicit Error Handling
No
try/catch
Blocks:
The script executes complex network operations (
loadDocuments
, Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.
Good response
Bad response
Redo
Share & export
Copy
Show more options
Ask Gemini
Ask Gemini
Upload & tools
Microphone
Send message...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
64690
|
2265
|
66
|
2026-05-20T14:12:10.485329+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779286330485_m2.jpg...
|
Firefox
|
JY-20676 delete AJ reports related objects by Laky JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app — Work...
|
1
|
github.com/jiminny/app/pull/12098
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.10721409,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.15791224,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.1245012,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.1796875,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.12699468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":4,"bounds":{"left":0.0,"top":0.21548285,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":5,"bounds":{"left":0.013297873,"top":0.22665602,"width":0.10139628,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"bounds":{"left":0.0,"top":0.2482043,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.25937748,"width":0.18068483,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.28092578,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.29209897,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.31364724,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.32482043,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.3463687,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.3575419,"width":0.15824468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.38068634,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"bounds":{"left":0.35854387,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"bounds":{"left":0.37051198,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Main menu","depth":13,"bounds":{"left":0.08228058,"top":0.09736632,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New chat","depth":12,"bounds":{"left":0.09823803,"top":0.10215483,"width":0.028091755,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":15,"bounds":{"left":0.1008976,"top":0.10654429,"width":0.02144282,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Chat","depth":13,"bounds":{"left":0.35854387,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":13,"bounds":{"left":0.37051198,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"bounds":{"left":0.079288565,"top":0.14445332,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"bounds":{"left":0.079288565,"top":0.14684756,"width":0.1200133,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"You said I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a concise summary of the changes made in the code selection:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Refactoring and Dependencies","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Refactoring and Dependencies","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Replaced Interface with Implementation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusInterface","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusManager","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in the imports and constructor dependency injection.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Redis Support:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Imported","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Illuminate\\Support\\Facades\\Redis","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to handle a newly introduced memory-logging lock mechanism.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Traits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WorkerIdTrait","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", along with the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"worker","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"field tracking in the final logs.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Loop and Worker Lifecycle Adjustments","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Loop and Worker Lifecycle Adjustments","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Worker Auto-Restart:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Deleted the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property, the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"constant, and the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"shouldIterateAgain()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method. The script no longer tracks execution iterations or triggers a self-managed worker restart.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Simplified Chunk Method Return:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Modified","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to always return","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Priority Handling for Throttled Entities","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Priority Handling for Throttled Entities","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Stale Record Elevation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Updated the logic in","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"rescheduleThrottledEntities()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to check if a throttled entity group is classified as \"stale\". If it is, the entities are rescheduled using","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"QueuePriorityEnum::NORMAL","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"; otherwise, they keep their respective queue priority.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Metrics, Logging, and Environment Handling","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Metrics, Logging, and Environment Handling","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Environment Tracking:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Introduced a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$environment","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property (defaulting to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'local'","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") and a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"setEnvironment()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method to dynamically assign it.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Memory Monitoring via Datadog Gauge:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method that submits peak memory allocation to Datadog as a gauge metric (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"jiminny.reindex-memory-usage","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis Lock for Metrics Throttling:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Implemented a Redis-backed lock (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"%s-process-manager-lock","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") with a 60-second expiration inside","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to prevent over-reporting metrics.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Log Payload Update:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added total processed items (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$updatedDocuments + $deletedDocuments","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") to the final information log.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":23,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said are there any issues or potential bottlenecks","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"are there any issues or potential bottlenecks","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a breakdown of the potential issues to look out for:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"1. Memory Leak Risk (OOM Crashes)","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. Memory Leak Risk (OOM Crashes)","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Restart Logic:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The most significant change is the removal of the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Dependency Leaks:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If any underlying service (like the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ElasticSearchClient","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", ORM, or Datadog helper) retains references to processed entities,","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"bounds":{"left":0.26811835,"top":0.0,"width":0.056848403,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23138298,"height":0.05546688},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Hardcoded Limits:","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.047041222,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The code still includes","depth":26,"bounds":{"left":0.17154256,"top":0.0,"width":0.05651596,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ini_set('memory_limit', '512M');","depth":27,"bounds":{"left":0.23005319,"top":0.0,"width":0.095744684,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23055187,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"2. Race Condition in Redis Lock","depth":23,"bounds":{"left":0.111369684,"top":0.06823623,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. Race Condition in Redis Lock","depth":24,"bounds":{"left":0.111369684,"top":0.06783719,"width":0.09125665,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Non-Atomic Operations:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.062333778,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The","depth":26,"bounds":{"left":0.18683511,"top":0.09497207,"width":0.011635638,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage","depth":27,"bounds":{"left":0.20046543,"top":0.096568234,"width":0.041888297,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method uses three distinct steps to handle the lock:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.22722739,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"get()","depth":27,"bounds":{"left":0.14860372,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":26,"bounds":{"left":0.16555852,"top":0.11412609,"width":0.002493351,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.17004654,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", and","depth":26,"bounds":{"left":0.18700133,"top":0.11412609,"width":0.013131649,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"expire()","depth":27,"bounds":{"left":0.20212767,"top":0.11572227,"width":0.023936171,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.22805852,"top":0.11412609,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If multiple workers process the same","depth":26,"bounds":{"left":0.16456117,"top":0.14285715,"width":0.09275266,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$this->updateTarget","depth":27,"bounds":{"left":0.25930852,"top":0.14445332,"width":0.056848403,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"concurrently, they can both pass the","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"if (! is_null(Redis::get(...)))","depth":27,"bounds":{"left":0.18351063,"top":0.16360734,"width":0.09275266,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"check at the exact same millisecond. Both will then execute","depth":26,"bounds":{"left":0.12450133,"top":0.16201118,"width":0.21575798,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.2122673,"top":0.18276137,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and push duplicate metrics to Datadog.","depth":26,"bounds":{"left":0.22922207,"top":0.1811652,"width":0.09823803,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Fix:","depth":26,"bounds":{"left":0.12450133,"top":0.20989625,"width":0.019281914,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This should be replaced with a single atomic operation using Redis's","depth":26,"bounds":{"left":0.14378324,"top":0.20989625,"width":0.16821809,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"NX","depth":27,"bounds":{"left":0.31399602,"top":0.21149242,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Not eXists) and","depth":26,"bounds":{"left":0.12450133,"top":0.20989625,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"EX","depth":27,"bounds":{"left":0.13696809,"top":0.23064645,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Expire) flags, or Laravel’s built-in atomic cache locks:","depth":26,"bounds":{"left":0.14494681,"top":0.22905028,"width":0.13264628,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis::set($gaugeLockName, true, 'EX', 60, 'NX')","depth":27,"bounds":{"left":0.12649602,"top":0.24980047,"width":0.14361702,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.27210772,"top":0.2482043,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"3. Infinite Loops and Caller Contract","depth":23,"bounds":{"left":0.111369684,"top":0.2980846,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. Infinite Loops and Caller Contract","depth":24,"bounds":{"left":0.111369684,"top":0.29768556,"width":0.10571808,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Return Value Change:","depth":26,"bounds":{"left":0.12450133,"top":0.32482043,"width":0.055352394,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-5042768006313656563
|
-1061874711537616932
|
visual_change
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:...
|
64689
|
NULL
|
NULL
|
NULL
|
|
64691
|
2264
|
29
|
2026-05-20T14:12:34.196963+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779286354196_m1.jpg...
|
Firefox
|
JY-20676 delete AJ reports related objects by Laky JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app — Work...
|
1
|
github.com/jiminny/app/pull/12098
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Main menu","depth":13,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New chat","depth":12,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Chat","depth":13,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":13,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"You said I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a concise summary of the changes made in the code selection:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Refactoring and Dependencies","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Refactoring and Dependencies","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Replaced Interface with Implementation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusInterface","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusManager","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in the imports and constructor dependency injection.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Redis Support:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Imported","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Illuminate\\Support\\Facades\\Redis","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to handle a newly introduced memory-logging lock mechanism.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Traits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WorkerIdTrait","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", along with the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"worker","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"field tracking in the final logs.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Loop and Worker Lifecycle Adjustments","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Loop and Worker Lifecycle Adjustments","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Worker Auto-Restart:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Deleted the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property, the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"constant, and the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"shouldIterateAgain()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method. The script no longer tracks execution iterations or triggers a self-managed worker restart.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Simplified Chunk Method Return:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Modified","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to always return","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Priority Handling for Throttled Entities","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Priority Handling for Throttled Entities","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Stale Record Elevation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Updated the logic in","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"rescheduleThrottledEntities()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to check if a throttled entity group is classified as \"stale\". If it is, the entities are rescheduled using","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"QueuePriorityEnum::NORMAL","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"; otherwise, they keep their respective queue priority.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Metrics, Logging, and Environment Handling","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Metrics, Logging, and Environment Handling","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Environment Tracking:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Introduced a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$environment","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property (defaulting to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'local'","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") and a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"setEnvironment()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method to dynamically assign it.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Memory Monitoring via Datadog Gauge:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method that submits peak memory allocation to Datadog as a gauge metric (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"jiminny.reindex-memory-usage","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis Lock for Metrics Throttling:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Implemented a Redis-backed lock (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"%s-process-manager-lock","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") with a 60-second expiration inside","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to prevent over-reporting metrics.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Log Payload Update:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added total processed items (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$updatedDocuments + $deletedDocuments","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") to the final information log.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":23,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said are there any issues or potential bottlenecks","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"are there any issues or potential bottlenecks","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a breakdown of the potential issues to look out for:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"1. Memory Leak Risk (OOM Crashes)","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. Memory Leak Risk (OOM Crashes)","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Restart Logic:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The most significant change is the removal of the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Dependency Leaks:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If any underlying service (like the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ElasticSearchClient","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", ORM, or Datadog helper) retains references to processed entities,","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Hardcoded Limits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The code still includes","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ini_set('memory_limit', '512M');","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"2. Race Condition in Redis Lock","depth":23,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. Race Condition in Redis Lock","depth":24,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Non-Atomic Operations:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method uses three distinct steps to handle the lock:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"get()","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", and","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"expire()","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If multiple workers process the same","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$this->updateTarget","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"concurrently, they can both pass the","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-3304065254231117893
|
-1061874711537616996
|
idle
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the...
|
64687
|
NULL
|
NULL
|
NULL
|
|
64692
|
2265
|
67
|
2026-05-20T14:12:41.030032+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779286361030_m2.jpg...
|
Firefox
|
JY-20676 delete AJ reports related objects by Laky JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app — Work...
|
1
|
github.com/jiminny/app/pull/12098
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage.
4. Performance Overhead
4. Performance Overhead
Expensive Garbage Collection:
Calling
gc_collect_cycles();
on
every single chunk
can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.
Synchronous Metric Dispatch:
If the
Datadog::increment
or
Datadog::gauge
methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.
5. Lack of Explicit Error Handling
5. Lack of Explicit Error Handling
No
try/catch
Blocks:
The script executes complex network operations (
loadDocuments
, Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.
Good response
Bad response
Redo
Share & export
Copy
Show more options
Ask Gemini
Ask Gemini...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.10721409,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.15791224,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.1245012,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.1796875,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.12699468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":4,"bounds":{"left":0.0,"top":0.21548285,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":5,"bounds":{"left":0.013297873,"top":0.22665602,"width":0.10139628,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"bounds":{"left":0.0,"top":0.2482043,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.25937748,"width":0.18068483,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.28092578,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.29209897,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.31364724,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.32482043,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.3463687,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.3575419,"width":0.15824468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.38068634,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"bounds":{"left":0.35854387,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"bounds":{"left":0.37051198,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Main menu","depth":13,"bounds":{"left":0.08228058,"top":0.09736632,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New chat","depth":12,"bounds":{"left":0.09823803,"top":0.10215483,"width":0.028091755,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":15,"bounds":{"left":0.1008976,"top":0.10654429,"width":0.02144282,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Chat","depth":13,"bounds":{"left":0.35854387,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":13,"bounds":{"left":0.37051198,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"bounds":{"left":0.079288565,"top":0.14445332,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"bounds":{"left":0.079288565,"top":0.14684756,"width":0.1200133,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"You said I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a concise summary of the changes made in the code selection:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Refactoring and Dependencies","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Refactoring and Dependencies","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Replaced Interface with Implementation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusInterface","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusManager","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in the imports and constructor dependency injection.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Redis Support:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Imported","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Illuminate\\Support\\Facades\\Redis","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to handle a newly introduced memory-logging lock mechanism.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Traits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WorkerIdTrait","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", along with the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"worker","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"field tracking in the final logs.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Loop and Worker Lifecycle Adjustments","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Loop and Worker Lifecycle Adjustments","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Worker Auto-Restart:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Deleted the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property, the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"constant, and the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"shouldIterateAgain()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method. The script no longer tracks execution iterations or triggers a self-managed worker restart.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Simplified Chunk Method Return:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Modified","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to always return","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Priority Handling for Throttled Entities","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Priority Handling for Throttled Entities","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Stale Record Elevation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Updated the logic in","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"rescheduleThrottledEntities()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to check if a throttled entity group is classified as \"stale\". If it is, the entities are rescheduled using","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"QueuePriorityEnum::NORMAL","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"; otherwise, they keep their respective queue priority.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Metrics, Logging, and Environment Handling","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Metrics, Logging, and Environment Handling","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Environment Tracking:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Introduced a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$environment","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property (defaulting to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'local'","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") and a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"setEnvironment()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method to dynamically assign it.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Memory Monitoring via Datadog Gauge:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method that submits peak memory allocation to Datadog as a gauge metric (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"jiminny.reindex-memory-usage","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis Lock for Metrics Throttling:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Implemented a Redis-backed lock (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"%s-process-manager-lock","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") with a 60-second expiration inside","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to prevent over-reporting metrics.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Log Payload Update:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added total processed items (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$updatedDocuments + $deletedDocuments","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") to the final information log.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":23,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said are there any issues or potential bottlenecks","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"are there any issues or potential bottlenecks","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a breakdown of the potential issues to look out for:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"1. Memory Leak Risk (OOM Crashes)","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. Memory Leak Risk (OOM Crashes)","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Restart Logic:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The most significant change is the removal of the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Dependency Leaks:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If any underlying service (like the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ElasticSearchClient","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", ORM, or Datadog helper) retains references to processed entities,","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"bounds":{"left":0.26811835,"top":0.0,"width":0.056848403,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23138298,"height":0.05546688},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Hardcoded Limits:","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.047041222,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The code still includes","depth":26,"bounds":{"left":0.17154256,"top":0.0,"width":0.05651596,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ini_set('memory_limit', '512M');","depth":27,"bounds":{"left":0.23005319,"top":0.0,"width":0.095744684,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23055187,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"2. Race Condition in Redis Lock","depth":23,"bounds":{"left":0.111369684,"top":0.06823623,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. Race Condition in Redis Lock","depth":24,"bounds":{"left":0.111369684,"top":0.06783719,"width":0.09125665,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Non-Atomic Operations:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.062333778,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The","depth":26,"bounds":{"left":0.18683511,"top":0.09497207,"width":0.011635638,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage","depth":27,"bounds":{"left":0.20046543,"top":0.096568234,"width":0.041888297,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method uses three distinct steps to handle the lock:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.22722739,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"get()","depth":27,"bounds":{"left":0.14860372,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":26,"bounds":{"left":0.16555852,"top":0.11412609,"width":0.002493351,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.17004654,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", and","depth":26,"bounds":{"left":0.18700133,"top":0.11412609,"width":0.013131649,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"expire()","depth":27,"bounds":{"left":0.20212767,"top":0.11572227,"width":0.023936171,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.22805852,"top":0.11412609,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If multiple workers process the same","depth":26,"bounds":{"left":0.16456117,"top":0.14285715,"width":0.09275266,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$this->updateTarget","depth":27,"bounds":{"left":0.25930852,"top":0.14445332,"width":0.056848403,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"concurrently, they can both pass the","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"if (! is_null(Redis::get(...)))","depth":27,"bounds":{"left":0.18351063,"top":0.16360734,"width":0.09275266,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"check at the exact same millisecond. Both will then execute","depth":26,"bounds":{"left":0.12450133,"top":0.16201118,"width":0.21575798,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.2122673,"top":0.18276137,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and push duplicate metrics to Datadog.","depth":26,"bounds":{"left":0.22922207,"top":0.1811652,"width":0.09823803,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Fix:","depth":26,"bounds":{"left":0.12450133,"top":0.20989625,"width":0.019281914,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This should be replaced with a single atomic operation using Redis's","depth":26,"bounds":{"left":0.14378324,"top":0.20989625,"width":0.16821809,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"NX","depth":27,"bounds":{"left":0.31399602,"top":0.21149242,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Not eXists) and","depth":26,"bounds":{"left":0.12450133,"top":0.20989625,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"EX","depth":27,"bounds":{"left":0.13696809,"top":0.23064645,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Expire) flags, or Laravel’s built-in atomic cache locks:","depth":26,"bounds":{"left":0.14494681,"top":0.22905028,"width":0.13264628,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis::set($gaugeLockName, true, 'EX', 60, 'NX')","depth":27,"bounds":{"left":0.12649602,"top":0.24980047,"width":0.14361702,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.27210772,"top":0.2482043,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"3. Infinite Loops and Caller Contract","depth":23,"bounds":{"left":0.111369684,"top":0.2980846,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. Infinite Loops and Caller Contract","depth":24,"bounds":{"left":0.111369684,"top":0.29768556,"width":0.10571808,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Return Value Change:","depth":26,"bounds":{"left":0.12450133,"top":0.32482043,"width":0.055352394,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"bounds":{"left":0.18301196,"top":0.3264166,"width":0.06881649,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"previously returned whether it should iterate again based on staleness or chunk size. It now always returns","depth":26,"bounds":{"left":0.12450133,"top":0.32482043,"width":0.2237367,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"bounds":{"left":0.29438165,"top":0.34557062,"width":0.011968086,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(unless the selection is totally empty).","depth":26,"bounds":{"left":0.12450133,"top":0.34397447,"width":0.2130984,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.39185953,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the code calling this method relies on a","depth":26,"bounds":{"left":0.16456117,"top":0.39185953,"width":0.10305851,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"false","depth":27,"bounds":{"left":0.26961437,"top":0.3934557,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"return to pause, sleep, or gracefully terminate, changing this to an unconditional","depth":26,"bounds":{"left":0.12450133,"top":0.39185953,"width":0.22589761,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"bounds":{"left":0.26163563,"top":0.41260973,"width":0.011968086,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"could result in an aggressive \"busy loop\" that spikes CPU usage.","depth":26,"bounds":{"left":0.12450133,"top":0.41101357,"width":0.22290559,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"4. Performance Overhead","depth":23,"bounds":{"left":0.111369684,"top":0.48004788,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4. Performance Overhead","depth":24,"bounds":{"left":0.111369684,"top":0.47964883,"width":0.076130316,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Expensive Garbage Collection:","depth":26,"bounds":{"left":0.12450133,"top":0.5067837,"width":0.078457445,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Calling","depth":26,"bounds":{"left":0.20295878,"top":0.5067837,"width":0.019281914,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles();","depth":27,"bounds":{"left":0.22423537,"top":0.5083799,"width":0.059840426,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"on","depth":26,"bounds":{"left":0.28607047,"top":0.5067837,"width":0.00880984,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"every single chunk","depth":26,"bounds":{"left":0.29488033,"top":0.5067837,"width":0.045545213,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.","depth":26,"bounds":{"left":0.12450133,"top":0.5067837,"width":0.23420878,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Synchronous Metric Dispatch:","depth":26,"bounds":{"left":0.12450133,"top":0.5738228,"width":0.07712766,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the","depth":26,"bounds":{"left":0.20162898,"top":0.5738228,"width":0.015292553,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Datadog::increment","depth":27,"bounds":{"left":0.21891622,"top":0.575419,"width":0.053856384,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"or","depth":26,"bounds":{"left":0.27476728,"top":0.5738228,"width":0.0078125,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Datadog::gauge","depth":27,"bounds":{"left":0.28457448,"top":0.575419,"width":0.041888297,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.","depth":26,"bounds":{"left":0.12450133,"top":0.5738228,"width":0.23420878,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"5. Lack of Explicit Error Handling","depth":23,"bounds":{"left":0.111369684,"top":0.66201115,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5. Lack of Explicit Error Handling","depth":24,"bounds":{"left":0.111369684,"top":0.66161215,"width":0.094913565,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No","depth":26,"bounds":{"left":0.12450133,"top":0.688747,"width":0.008477394,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"try/catch","depth":27,"bounds":{"left":0.1349734,"top":0.6903432,"width":0.026928192,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Blocks:","depth":26,"bounds":{"left":0.16389628,"top":0.688747,"width":0.019614361,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The script executes complex network operations (","depth":26,"bounds":{"left":0.18351063,"top":0.688747,"width":0.12367021,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"loadDocuments","depth":27,"bounds":{"left":0.30917552,"top":0.6903432,"width":0.038896278,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.","depth":26,"bounds":{"left":0.12450133,"top":0.688747,"width":0.22888963,"height":0.09377494},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":22,"bounds":{"left":0.1087101,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":22,"bounds":{"left":0.124667555,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Redo","depth":22,"bounds":{"left":0.140625,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":21,"bounds":{"left":0.15658244,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":22,"bounds":{"left":0.17253989,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":21,"bounds":{"left":0.18849733,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXTextArea","text":"Ask Gemini","depth":21,"bounds":{"left":0.14594415,"top":0.86632085,"width":0.17021276,"height":0.01915403},"on_screen":true,"value":"Ask Gemini","help_text":"","role_description":"text entry area","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Ask Gemini","depth":22,"bounds":{"left":0.14594415,"top":0.8667199,"width":0.030418882,"height":0.018355945},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
1062163830408800351
|
-1070952279540680932
|
idle
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage.
4. Performance Overhead
4. Performance Overhead
Expensive Garbage Collection:
Calling
gc_collect_cycles();
on
every single chunk
can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.
Synchronous Metric Dispatch:
If the
Datadog::increment
or
Datadog::gauge
methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.
5. Lack of Explicit Error Handling
5. Lack of Explicit Error Handling
No
try/catch
Blocks:
The script executes complex network operations (
loadDocuments
, Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.
Good response
Bad response
Redo
Share & export
Copy
Show more options
Ask Gemini
Ask Gemini...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
64693
|
NULL
|
0
|
2026-05-20T14:13:04.588194+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779286384588_m1.jpg...
|
Firefox
|
JY-20676 delete AJ reports related objects by Laky JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app — Work...
|
1
|
github.com/jiminny/app/pull/12098
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Main menu","depth":13,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New chat","depth":12,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Chat","depth":13,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":13,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"You said I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a concise summary of the changes made in the code selection:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Refactoring and Dependencies","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Refactoring and Dependencies","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Replaced Interface with Implementation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusInterface","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusManager","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in the imports and constructor dependency injection.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Redis Support:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Imported","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Illuminate\\Support\\Facades\\Redis","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to handle a newly introduced memory-logging lock mechanism.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Traits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WorkerIdTrait","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", along with the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"worker","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"field tracking in the final logs.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Loop and Worker Lifecycle Adjustments","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Loop and Worker Lifecycle Adjustments","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Worker Auto-Restart:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Deleted the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property, the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"constant, and the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"shouldIterateAgain()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method. The script no longer tracks execution iterations or triggers a self-managed worker restart.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Simplified Chunk Method Return:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Modified","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to always return","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Priority Handling for Throttled Entities","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Priority Handling for Throttled Entities","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Stale Record Elevation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Updated the logic in","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"rescheduleThrottledEntities()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to check if a throttled entity group is classified as \"stale\". If it is, the entities are rescheduled using","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"QueuePriorityEnum::NORMAL","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"; otherwise, they keep their respective queue priority.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Metrics, Logging, and Environment Handling","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Metrics, Logging, and Environment Handling","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Environment Tracking:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Introduced a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$environment","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property (defaulting to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'local'","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") and a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"setEnvironment()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method to dynamically assign it.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Memory Monitoring via Datadog Gauge:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method that submits peak memory allocation to Datadog as a gauge metric (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"jiminny.reindex-memory-usage","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis Lock for Metrics Throttling:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Implemented a Redis-backed lock (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"%s-process-manager-lock","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") with a 60-second expiration inside","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to prevent over-reporting metrics.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Log Payload Update:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added total processed items (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$updatedDocuments + $deletedDocuments","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") to the final information log.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":23,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said are there any issues or potential bottlenecks","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"are there any issues or potential bottlenecks","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a breakdown of the potential issues to look out for:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"1. Memory Leak Risk (OOM Crashes)","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. Memory Leak Risk (OOM Crashes)","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Restart Logic:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The most significant change is the removal of the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Dependency Leaks:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If any underlying service (like the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ElasticSearchClient","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", ORM, or Datadog helper) retains references to processed entities,","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Hardcoded Limits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The code still includes","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ini_set('memory_limit', '512M');","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"2. Race Condition in Redis Lock","depth":23,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. Race Condition in Redis Lock","depth":24,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Non-Atomic Operations:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method uses three distinct steps to handle the lock:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"get()","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", and","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"expire()","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If multiple workers process the same","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$this->updateTarget","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"concurrently, they can both pass the","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"if (! is_null(Redis::get(...)))","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"check at the exact same millisecond. Both will then execute","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and push duplicate metrics to Datadog.","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Fix:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This should be replaced with a single atomic operation using Redis's","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"NX","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Not eXists) and","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"EX","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Expire) flags, or Laravel’s built-in atomic cache locks:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis::set($gaugeLockName, true, 'EX', 60, 'NX')","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"3. Infinite Loops and Caller Contract","depth":23,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. Infinite Loops and Caller Contract","depth":24,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Return Value Change:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"previously returned whether it should iterate again based on staleness or chunk size. It now always returns","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-3789728784885741914
|
-1079889110047098916
|
idle
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
64694
|
NULL
|
0
|
2026-05-20T14:13:11.536299+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779286391536_m2.jpg...
|
Firefox
|
JY-20676 delete AJ reports related objects by Laky JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app — Work...
|
1
|
github.com/jiminny/app/pull/12098
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.10721409,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.15791224,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.1245012,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.1796875,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.12699468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":4,"bounds":{"left":0.0,"top":0.21548285,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":5,"bounds":{"left":0.013297873,"top":0.22665602,"width":0.10139628,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"bounds":{"left":0.0,"top":0.2482043,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.25937748,"width":0.18068483,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.28092578,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.29209897,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.31364724,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.32482043,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.3463687,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.3575419,"width":0.15824468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.38068634,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"bounds":{"left":0.35854387,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"bounds":{"left":0.37051198,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Main menu","depth":13,"bounds":{"left":0.08228058,"top":0.09736632,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New chat","depth":12,"bounds":{"left":0.09823803,"top":0.10215483,"width":0.028091755,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":15,"bounds":{"left":0.1008976,"top":0.10654429,"width":0.02144282,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Chat","depth":13,"bounds":{"left":0.35854387,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":13,"bounds":{"left":0.37051198,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"bounds":{"left":0.079288565,"top":0.14445332,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"bounds":{"left":0.079288565,"top":0.14684756,"width":0.1200133,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"You said I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a concise summary of the changes made in the code selection:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Refactoring and Dependencies","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Refactoring and Dependencies","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Replaced Interface with Implementation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusInterface","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusManager","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in the imports and constructor dependency injection.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Redis Support:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Imported","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Illuminate\\Support\\Facades\\Redis","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to handle a newly introduced memory-logging lock mechanism.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Traits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WorkerIdTrait","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", along with the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"worker","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"field tracking in the final logs.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Loop and Worker Lifecycle Adjustments","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Loop and Worker Lifecycle Adjustments","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Worker Auto-Restart:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Deleted the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property, the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"constant, and the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"shouldIterateAgain()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method. The script no longer tracks execution iterations or triggers a self-managed worker restart.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Simplified Chunk Method Return:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Modified","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to always return","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Priority Handling for Throttled Entities","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Priority Handling for Throttled Entities","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Stale Record Elevation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Updated the logic in","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"rescheduleThrottledEntities()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to check if a throttled entity group is classified as \"stale\". If it is, the entities are rescheduled using","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"QueuePriorityEnum::NORMAL","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"; otherwise, they keep their respective queue priority.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Metrics, Logging, and Environment Handling","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Metrics, Logging, and Environment Handling","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Environment Tracking:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Introduced a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$environment","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property (defaulting to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'local'","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") and a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"setEnvironment()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method to dynamically assign it.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Memory Monitoring via Datadog Gauge:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method that submits peak memory allocation to Datadog as a gauge metric (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"jiminny.reindex-memory-usage","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis Lock for Metrics Throttling:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Implemented a Redis-backed lock (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"%s-process-manager-lock","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") with a 60-second expiration inside","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to prevent over-reporting metrics.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Log Payload Update:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added total processed items (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$updatedDocuments + $deletedDocuments","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") to the final information log.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":23,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said are there any issues or potential bottlenecks","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"are there any issues or potential bottlenecks","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a breakdown of the potential issues to look out for:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"1. Memory Leak Risk (OOM Crashes)","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. Memory Leak Risk (OOM Crashes)","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Restart Logic:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The most significant change is the removal of the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
2165610740144057478
|
-917759493397006436
|
idle
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter...
|
64692
|
NULL
|
NULL
|
NULL
|
|
64695
|
2266
|
0
|
2026-05-20T14:13:35.015697+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779286415015_m1.jpg...
|
Firefox
|
JY-20676 delete AJ reports related objects by Laky JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app — Work...
|
1
|
github.com/jiminny/app/pull/12098
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage.
4. Performance Overhead
4. Performance Overhead
Expensive Garbage Collection:
Calling
gc_collect_cycles();
on
every single chunk
can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.
Synchronous Metric Dispatch:
If the
Datadog::increment
or
Datadog::gauge
methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.
5. Lack of Explicit Error Handling
5. Lack of Explicit Error Handling
No
try/catch
Blocks:
The script executes complex network operations (
loadDocuments
, Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.
Good response
Bad response...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Main menu","depth":13,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New chat","depth":12,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Chat","depth":13,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":13,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"You said I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a concise summary of the changes made in the code selection:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Refactoring and Dependencies","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Refactoring and Dependencies","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Replaced Interface with Implementation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusInterface","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusManager","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in the imports and constructor dependency injection.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Redis Support:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Imported","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Illuminate\\Support\\Facades\\Redis","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to handle a newly introduced memory-logging lock mechanism.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Traits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WorkerIdTrait","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", along with the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"worker","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"field tracking in the final logs.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Loop and Worker Lifecycle Adjustments","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Loop and Worker Lifecycle Adjustments","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Worker Auto-Restart:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Deleted the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property, the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"constant, and the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"shouldIterateAgain()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method. The script no longer tracks execution iterations or triggers a self-managed worker restart.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Simplified Chunk Method Return:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Modified","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to always return","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Priority Handling for Throttled Entities","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Priority Handling for Throttled Entities","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Stale Record Elevation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Updated the logic in","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"rescheduleThrottledEntities()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to check if a throttled entity group is classified as \"stale\". If it is, the entities are rescheduled using","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"QueuePriorityEnum::NORMAL","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"; otherwise, they keep their respective queue priority.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Metrics, Logging, and Environment Handling","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Metrics, Logging, and Environment Handling","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Environment Tracking:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Introduced a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$environment","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property (defaulting to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'local'","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") and a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"setEnvironment()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method to dynamically assign it.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Memory Monitoring via Datadog Gauge:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method that submits peak memory allocation to Datadog as a gauge metric (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"jiminny.reindex-memory-usage","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis Lock for Metrics Throttling:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Implemented a Redis-backed lock (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"%s-process-manager-lock","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") with a 60-second expiration inside","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to prevent over-reporting metrics.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Log Payload Update:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added total processed items (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$updatedDocuments + $deletedDocuments","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") to the final information log.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":23,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said are there any issues or potential bottlenecks","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"are there any issues or potential bottlenecks","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a breakdown of the potential issues to look out for:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"1. Memory Leak Risk (OOM Crashes)","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. Memory Leak Risk (OOM Crashes)","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Restart Logic:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The most significant change is the removal of the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Dependency Leaks:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If any underlying service (like the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ElasticSearchClient","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", ORM, or Datadog helper) retains references to processed entities,","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Hardcoded Limits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The code still includes","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ini_set('memory_limit', '512M');","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"2. Race Condition in Redis Lock","depth":23,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. Race Condition in Redis Lock","depth":24,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Non-Atomic Operations:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method uses three distinct steps to handle the lock:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"get()","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", and","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"expire()","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If multiple workers process the same","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$this->updateTarget","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"concurrently, they can both pass the","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"if (! is_null(Redis::get(...)))","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"check at the exact same millisecond. Both will then execute","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and push duplicate metrics to Datadog.","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Fix:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This should be replaced with a single atomic operation using Redis's","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"NX","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Not eXists) and","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"EX","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Expire) flags, or Laravel’s built-in atomic cache locks:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis::set($gaugeLockName, true, 'EX', 60, 'NX')","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"3. Infinite Loops and Caller Contract","depth":23,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. Infinite Loops and Caller Contract","depth":24,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Return Value Change:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"previously returned whether it should iterate again based on staleness or chunk size. It now always returns","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(unless the selection is totally empty).","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the code calling this method relies on a","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"false","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"return to pause, sleep, or gracefully terminate, changing this to an unconditional","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"could result in an aggressive \"busy loop\" that spikes CPU usage.","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"4. Performance Overhead","depth":23,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4. Performance Overhead","depth":24,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Expensive Garbage Collection:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Calling","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles();","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"on","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"every single chunk","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Synchronous Metric Dispatch:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Datadog::increment","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"or","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Datadog::gauge","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"5. Lack of Explicit Error Handling","depth":23,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5. Lack of Explicit Error Handling","depth":24,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"try/catch","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Blocks:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The script executes complex network operations (","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"loadDocuments","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":22,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":22,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
7512898529376068722
|
-1052937881027004452
|
idle
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage.
4. Performance Overhead
4. Performance Overhead
Expensive Garbage Collection:
Calling
gc_collect_cycles();
on
every single chunk
can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.
Synchronous Metric Dispatch:
If the
Datadog::increment
or
Datadog::gauge
methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.
5. Lack of Explicit Error Handling
5. Lack of Explicit Error Handling
No
try/catch
Blocks:
The script executes complex network operations (
loadDocuments
, Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.
Good response
Bad response...
|
64693
|
NULL
|
NULL
|
NULL
|
|
64696
|
2267
|
0
|
2026-05-20T14:13:42.341552+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779286422341_m2.jpg...
|
Firefox
|
JY-20676 delete AJ reports related objects by Laky JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app — Work...
|
1
|
github.com/jiminny/app/pull/12098
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage.
4. Performance Overhead
4. Performance Overhead
Expensive Garbage Collection:
Calling
gc_collect_cycles();
on
every single chunk
can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.
Synchronous Metric Dispatch:
If the
Datadog::increment
or
Datadog::gauge
methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.
5. Lack of Explicit Error Handling
5. Lack of Explicit Error Handling
No
try/catch
Blocks:
The script executes complex network operations (
loadDocuments
, Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.
Good response
Bad response
Redo
Share & export
Copy
Show more options
Ask Gemini
Ask Gemini
Upload & tools
Microphone
Send message
Your Jiminny chats aren’t used to improve our models. Gemini is AI. It can make mistakes, so double check it.
Your privacy & Gemini Opens in a new window
Your privacy & Gemini
Opens in a new window
Summarize page
Summarize page
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
app
app
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
All issues(g then i)
All pull requests
All repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Pull requests (31)
Pull requests...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.10721409,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.15791224,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.1245012,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.1796875,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.12699468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":4,"bounds":{"left":0.0,"top":0.21548285,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":5,"bounds":{"left":0.013297873,"top":0.22665602,"width":0.10139628,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"bounds":{"left":0.0,"top":0.2482043,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.25937748,"width":0.18068483,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.28092578,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.29209897,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.31364724,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.32482043,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.3463687,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.3575419,"width":0.15824468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.38068634,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"bounds":{"left":0.35854387,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"bounds":{"left":0.37051198,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Main menu","depth":13,"bounds":{"left":0.08228058,"top":0.09736632,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New chat","depth":12,"bounds":{"left":0.09823803,"top":0.10215483,"width":0.028091755,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":15,"bounds":{"left":0.1008976,"top":0.10654429,"width":0.02144282,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Chat","depth":13,"bounds":{"left":0.35854387,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":13,"bounds":{"left":0.37051198,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"bounds":{"left":0.079288565,"top":0.14445332,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"bounds":{"left":0.079288565,"top":0.14684756,"width":0.1200133,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"You said I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a concise summary of the changes made in the code selection:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Refactoring and Dependencies","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Refactoring and Dependencies","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Replaced Interface with Implementation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusInterface","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusManager","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in the imports and constructor dependency injection.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Redis Support:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Imported","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Illuminate\\Support\\Facades\\Redis","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to handle a newly introduced memory-logging lock mechanism.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Traits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WorkerIdTrait","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", along with the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"worker","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"field tracking in the final logs.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Loop and Worker Lifecycle Adjustments","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Loop and Worker Lifecycle Adjustments","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Worker Auto-Restart:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Deleted the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property, the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"constant, and the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"shouldIterateAgain()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method. The script no longer tracks execution iterations or triggers a self-managed worker restart.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Simplified Chunk Method Return:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Modified","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to always return","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Priority Handling for Throttled Entities","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Priority Handling for Throttled Entities","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Stale Record Elevation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Updated the logic in","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"rescheduleThrottledEntities()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to check if a throttled entity group is classified as \"stale\". If it is, the entities are rescheduled using","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"QueuePriorityEnum::NORMAL","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"; otherwise, they keep their respective queue priority.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Metrics, Logging, and Environment Handling","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Metrics, Logging, and Environment Handling","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Environment Tracking:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Introduced a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$environment","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property (defaulting to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'local'","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") and a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"setEnvironment()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method to dynamically assign it.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Memory Monitoring via Datadog Gauge:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method that submits peak memory allocation to Datadog as a gauge metric (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"jiminny.reindex-memory-usage","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis Lock for Metrics Throttling:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Implemented a Redis-backed lock (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"%s-process-manager-lock","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") with a 60-second expiration inside","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to prevent over-reporting metrics.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Log Payload Update:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added total processed items (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$updatedDocuments + $deletedDocuments","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") to the final information log.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":23,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said are there any issues or potential bottlenecks","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"are there any issues or potential bottlenecks","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a breakdown of the potential issues to look out for:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"1. Memory Leak Risk (OOM Crashes)","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. Memory Leak Risk (OOM Crashes)","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Restart Logic:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The most significant change is the removal of the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Dependency Leaks:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If any underlying service (like the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ElasticSearchClient","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", ORM, or Datadog helper) retains references to processed entities,","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"bounds":{"left":0.26811835,"top":0.0,"width":0.056848403,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23138298,"height":0.05546688},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Hardcoded Limits:","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.047041222,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The code still includes","depth":26,"bounds":{"left":0.17154256,"top":0.0,"width":0.05651596,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ini_set('memory_limit', '512M');","depth":27,"bounds":{"left":0.23005319,"top":0.0,"width":0.095744684,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23055187,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"2. Race Condition in Redis Lock","depth":23,"bounds":{"left":0.111369684,"top":0.06823623,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. Race Condition in Redis Lock","depth":24,"bounds":{"left":0.111369684,"top":0.06783719,"width":0.09125665,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Non-Atomic Operations:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.062333778,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The","depth":26,"bounds":{"left":0.18683511,"top":0.09497207,"width":0.011635638,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage","depth":27,"bounds":{"left":0.20046543,"top":0.096568234,"width":0.041888297,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method uses three distinct steps to handle the lock:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.22722739,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"get()","depth":27,"bounds":{"left":0.14860372,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":26,"bounds":{"left":0.16555852,"top":0.11412609,"width":0.002493351,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.17004654,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", and","depth":26,"bounds":{"left":0.18700133,"top":0.11412609,"width":0.013131649,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"expire()","depth":27,"bounds":{"left":0.20212767,"top":0.11572227,"width":0.023936171,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.22805852,"top":0.11412609,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If multiple workers process the same","depth":26,"bounds":{"left":0.16456117,"top":0.14285715,"width":0.09275266,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$this->updateTarget","depth":27,"bounds":{"left":0.25930852,"top":0.14445332,"width":0.056848403,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"concurrently, they can both pass the","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"if (! is_null(Redis::get(...)))","depth":27,"bounds":{"left":0.18351063,"top":0.16360734,"width":0.09275266,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"check at the exact same millisecond. Both will then execute","depth":26,"bounds":{"left":0.12450133,"top":0.16201118,"width":0.21575798,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.2122673,"top":0.18276137,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and push duplicate metrics to Datadog.","depth":26,"bounds":{"left":0.22922207,"top":0.1811652,"width":0.09823803,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Fix:","depth":26,"bounds":{"left":0.12450133,"top":0.20989625,"width":0.019281914,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This should be replaced with a single atomic operation using Redis's","depth":26,"bounds":{"left":0.14378324,"top":0.20989625,"width":0.16821809,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"NX","depth":27,"bounds":{"left":0.31399602,"top":0.21149242,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Not eXists) and","depth":26,"bounds":{"left":0.12450133,"top":0.20989625,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"EX","depth":27,"bounds":{"left":0.13696809,"top":0.23064645,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Expire) flags, or Laravel’s built-in atomic cache locks:","depth":26,"bounds":{"left":0.14494681,"top":0.22905028,"width":0.13264628,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis::set($gaugeLockName, true, 'EX', 60, 'NX')","depth":27,"bounds":{"left":0.12649602,"top":0.24980047,"width":0.14361702,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.27210772,"top":0.2482043,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"3. Infinite Loops and Caller Contract","depth":23,"bounds":{"left":0.111369684,"top":0.2980846,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. Infinite Loops and Caller Contract","depth":24,"bounds":{"left":0.111369684,"top":0.29768556,"width":0.10571808,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Return Value Change:","depth":26,"bounds":{"left":0.12450133,"top":0.32482043,"width":0.055352394,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"bounds":{"left":0.18301196,"top":0.3264166,"width":0.06881649,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"previously returned whether it should iterate again based on staleness or chunk size. It now always returns","depth":26,"bounds":{"left":0.12450133,"top":0.32482043,"width":0.2237367,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"bounds":{"left":0.29438165,"top":0.34557062,"width":0.011968086,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(unless the selection is totally empty).","depth":26,"bounds":{"left":0.12450133,"top":0.34397447,"width":0.2130984,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.39185953,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the code calling this method relies on a","depth":26,"bounds":{"left":0.16456117,"top":0.39185953,"width":0.10305851,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"false","depth":27,"bounds":{"left":0.26961437,"top":0.3934557,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"return to pause, sleep, or gracefully terminate, changing this to an unconditional","depth":26,"bounds":{"left":0.12450133,"top":0.39185953,"width":0.22589761,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"bounds":{"left":0.26163563,"top":0.41260973,"width":0.011968086,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"could result in an aggressive \"busy loop\" that spikes CPU usage.","depth":26,"bounds":{"left":0.12450133,"top":0.41101357,"width":0.22290559,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"4. Performance Overhead","depth":23,"bounds":{"left":0.111369684,"top":0.48004788,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4. Performance Overhead","depth":24,"bounds":{"left":0.111369684,"top":0.47964883,"width":0.076130316,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Expensive Garbage Collection:","depth":26,"bounds":{"left":0.12450133,"top":0.5067837,"width":0.078457445,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Calling","depth":26,"bounds":{"left":0.20295878,"top":0.5067837,"width":0.019281914,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles();","depth":27,"bounds":{"left":0.22423537,"top":0.5083799,"width":0.059840426,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"on","depth":26,"bounds":{"left":0.28607047,"top":0.5067837,"width":0.00880984,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"every single chunk","depth":26,"bounds":{"left":0.29488033,"top":0.5067837,"width":0.045545213,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.","depth":26,"bounds":{"left":0.12450133,"top":0.5067837,"width":0.23420878,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Synchronous Metric Dispatch:","depth":26,"bounds":{"left":0.12450133,"top":0.5738228,"width":0.07712766,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the","depth":26,"bounds":{"left":0.20162898,"top":0.5738228,"width":0.015292553,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Datadog::increment","depth":27,"bounds":{"left":0.21891622,"top":0.575419,"width":0.053856384,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"or","depth":26,"bounds":{"left":0.27476728,"top":0.5738228,"width":0.0078125,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Datadog::gauge","depth":27,"bounds":{"left":0.28457448,"top":0.575419,"width":0.041888297,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.","depth":26,"bounds":{"left":0.12450133,"top":0.5738228,"width":0.23420878,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"5. Lack of Explicit Error Handling","depth":23,"bounds":{"left":0.111369684,"top":0.66201115,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5. Lack of Explicit Error Handling","depth":24,"bounds":{"left":0.111369684,"top":0.66161215,"width":0.094913565,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No","depth":26,"bounds":{"left":0.12450133,"top":0.688747,"width":0.008477394,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"try/catch","depth":27,"bounds":{"left":0.1349734,"top":0.6903432,"width":0.026928192,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Blocks:","depth":26,"bounds":{"left":0.16389628,"top":0.688747,"width":0.019614361,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The script executes complex network operations (","depth":26,"bounds":{"left":0.18351063,"top":0.688747,"width":0.12367021,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"loadDocuments","depth":27,"bounds":{"left":0.30917552,"top":0.6903432,"width":0.038896278,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.","depth":26,"bounds":{"left":0.12450133,"top":0.688747,"width":0.22888963,"height":0.09377494},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":22,"bounds":{"left":0.1087101,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":22,"bounds":{"left":0.124667555,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Redo","depth":22,"bounds":{"left":0.140625,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":21,"bounds":{"left":0.15658244,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":22,"bounds":{"left":0.17253989,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":21,"bounds":{"left":0.18849733,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXTextArea","text":"Ask Gemini","depth":21,"bounds":{"left":0.14594415,"top":0.86632085,"width":0.17021276,"height":0.01915403},"on_screen":true,"value":"Ask Gemini","help_text":"","role_description":"text entry area","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Ask Gemini","depth":22,"bounds":{"left":0.14594415,"top":0.8667199,"width":0.030418882,"height":0.018355945},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Upload & tools","depth":20,"bounds":{"left":0.12732713,"top":0.8567438,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Microphone","depth":20,"bounds":{"left":0.32413563,"top":0.86153233,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Send message","depth":20,"bounds":{"left":0.33211437,"top":0.8631285,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Your Jiminny chats aren’t used to improve our models. Gemini is AI. It can make mistakes, so double check it.","depth":17,"bounds":{"left":0.102726065,"top":0.9173983,"width":0.21775267,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Your privacy & Gemini Opens in a new window","depth":17,"bounds":{"left":0.32047874,"top":0.9173983,"width":0.043882977,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Your privacy & Gemini","depth":18,"bounds":{"left":0.32047874,"top":0.9173983,"width":0.043882977,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Opens in a new window","depth":19,"bounds":{"left":0.079288565,"top":0.91660017,"width":0.04720745,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Summarize page","depth":7,"bounds":{"left":0.08494016,"top":0.95730245,"width":0.053523935,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Summarize page","depth":9,"bounds":{"left":0.09059176,"top":0.96249,"width":0.042220745,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Skip to content","depth":6,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to content","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open menu","depth":10,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Homepage (g then d)","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"jiminny","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"jiminny","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"app","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"app","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Search or jump to…","depth":9,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Type","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to search","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Chat with Copilot","depth":10,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Open Copilot…","depth":9,"on_screen":false,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Create new...","depth":9,"on_screen":false,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"All issues(g then i)","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"All pull requests","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"All repositories","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"You have unread notifications(g then n)","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open user navigation menu","depth":9,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Repository navigation","depth":9,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Repository navigation","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Code","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Code","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Pull requests (31)","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pull requests","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
5720481319462978766
|
-1052796009671460964
|
idle
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage.
4. Performance Overhead
4. Performance Overhead
Expensive Garbage Collection:
Calling
gc_collect_cycles();
on
every single chunk
can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.
Synchronous Metric Dispatch:
If the
Datadog::increment
or
Datadog::gauge
methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.
5. Lack of Explicit Error Handling
5. Lack of Explicit Error Handling
No
try/catch
Blocks:
The script executes complex network operations (
loadDocuments
, Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.
Good response
Bad response
Redo
Share & export
Copy
Show more options
Ask Gemini
Ask Gemini
Upload & tools
Microphone
Send message
Your Jiminny chats aren’t used to improve our models. Gemini is AI. It can make mistakes, so double check it.
Your privacy & Gemini Opens in a new window
Your privacy & Gemini
Opens in a new window
Summarize page
Summarize page
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
app
app
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
All issues(g then i)
All pull requests
All repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Pull requests (31)
Pull requests...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
64697
|
2267
|
1
|
2026-05-20T14:13:53.227582+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779286433227_m2.jpg...
|
Firefox
|
JY-20676 delete AJ reports related objects by Laky JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app — Work...
|
1
|
github.com/jiminny/app/pull/12098
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage.
4. Performance Overhead
4. Performance Overhead
Expensive Garbage Collection:
Calling
gc_collect_cycles();
on
every single chunk
can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.
Synchronous Metric Dispatch:
If the
Datadog::increment
or
Datadog::gauge
methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.
5. Lack of Explicit Error Handling
5. Lack of Explicit Error Handling
No
try/catch
Blocks:
The script executes complex network operations (
loadDocuments
, Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.
Good response
Bad response
Redo
Share & export
Copy
Show more options
Ask Gemini
Ask Gemini
Upload & tools
Microphone
Send message
Your Jiminny chats aren’t used to improve our models. Gemini is AI. It can make mistakes, so double check it.
Your privacy & Gemini Opens in a new window
Your privacy & Gemini
Opens in a new window
Summarize page
Summarize page
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
app
app
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
All issues(g then i)
All pull requests
All repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Pull requests (31)
Pull requests
(
31
)
Agents
Agents
Actions
Actions
Wiki
Wiki
Security and quality
Security and quality
Insights
Insights
Settings
Settings
JY-20676 delete AJ reports related objects #12098 Edit title
JY-20676 delete AJ reports related objects
#
12098
Edit title
Checks pending
Checks pending
Code
Code
Open...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.10721409,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.15791224,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.1245012,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.1796875,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.12699468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":4,"bounds":{"left":0.0,"top":0.21548285,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":5,"bounds":{"left":0.013297873,"top":0.22665602,"width":0.10139628,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"bounds":{"left":0.0,"top":0.2482043,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.25937748,"width":0.18068483,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.28092578,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.29209897,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.31364724,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.32482043,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.3463687,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.3575419,"width":0.15824468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.38068634,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"bounds":{"left":0.35854387,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"bounds":{"left":0.37051198,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Main menu","depth":13,"bounds":{"left":0.08228058,"top":0.09736632,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New chat","depth":12,"bounds":{"left":0.09823803,"top":0.10215483,"width":0.028091755,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":15,"bounds":{"left":0.1008976,"top":0.10654429,"width":0.02144282,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Chat","depth":13,"bounds":{"left":0.35854387,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":13,"bounds":{"left":0.37051198,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"bounds":{"left":0.079288565,"top":0.14445332,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"bounds":{"left":0.079288565,"top":0.14684756,"width":0.1200133,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"You said I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a concise summary of the changes made in the code selection:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Refactoring and Dependencies","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Refactoring and Dependencies","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Replaced Interface with Implementation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusInterface","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusManager","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in the imports and constructor dependency injection.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Redis Support:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Imported","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Illuminate\\Support\\Facades\\Redis","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to handle a newly introduced memory-logging lock mechanism.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Traits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WorkerIdTrait","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", along with the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"worker","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"field tracking in the final logs.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Loop and Worker Lifecycle Adjustments","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Loop and Worker Lifecycle Adjustments","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Worker Auto-Restart:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Deleted the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property, the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"constant, and the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"shouldIterateAgain()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method. The script no longer tracks execution iterations or triggers a self-managed worker restart.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Simplified Chunk Method Return:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Modified","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to always return","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Priority Handling for Throttled Entities","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Priority Handling for Throttled Entities","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Stale Record Elevation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Updated the logic in","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"rescheduleThrottledEntities()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to check if a throttled entity group is classified as \"stale\". If it is, the entities are rescheduled using","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"QueuePriorityEnum::NORMAL","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"; otherwise, they keep their respective queue priority.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Metrics, Logging, and Environment Handling","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Metrics, Logging, and Environment Handling","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Environment Tracking:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Introduced a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$environment","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property (defaulting to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'local'","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") and a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"setEnvironment()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method to dynamically assign it.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Memory Monitoring via Datadog Gauge:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method that submits peak memory allocation to Datadog as a gauge metric (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"jiminny.reindex-memory-usage","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis Lock for Metrics Throttling:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Implemented a Redis-backed lock (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"%s-process-manager-lock","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") with a 60-second expiration inside","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to prevent over-reporting metrics.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Log Payload Update:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added total processed items (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$updatedDocuments + $deletedDocuments","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") to the final information log.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":23,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said are there any issues or potential bottlenecks","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"are there any issues or potential bottlenecks","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a breakdown of the potential issues to look out for:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"1. Memory Leak Risk (OOM Crashes)","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. Memory Leak Risk (OOM Crashes)","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Restart Logic:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The most significant change is the removal of the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Dependency Leaks:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If any underlying service (like the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ElasticSearchClient","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", ORM, or Datadog helper) retains references to processed entities,","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"bounds":{"left":0.26811835,"top":0.0,"width":0.056848403,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23138298,"height":0.05546688},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Hardcoded Limits:","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.047041222,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The code still includes","depth":26,"bounds":{"left":0.17154256,"top":0.0,"width":0.05651596,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ini_set('memory_limit', '512M');","depth":27,"bounds":{"left":0.23005319,"top":0.0,"width":0.095744684,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23055187,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"2. Race Condition in Redis Lock","depth":23,"bounds":{"left":0.111369684,"top":0.06823623,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. Race Condition in Redis Lock","depth":24,"bounds":{"left":0.111369684,"top":0.06783719,"width":0.09125665,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Non-Atomic Operations:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.062333778,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The","depth":26,"bounds":{"left":0.18683511,"top":0.09497207,"width":0.011635638,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage","depth":27,"bounds":{"left":0.20046543,"top":0.096568234,"width":0.041888297,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method uses three distinct steps to handle the lock:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.22722739,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"get()","depth":27,"bounds":{"left":0.14860372,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":26,"bounds":{"left":0.16555852,"top":0.11412609,"width":0.002493351,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.17004654,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", and","depth":26,"bounds":{"left":0.18700133,"top":0.11412609,"width":0.013131649,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"expire()","depth":27,"bounds":{"left":0.20212767,"top":0.11572227,"width":0.023936171,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.22805852,"top":0.11412609,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If multiple workers process the same","depth":26,"bounds":{"left":0.16456117,"top":0.14285715,"width":0.09275266,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$this->updateTarget","depth":27,"bounds":{"left":0.25930852,"top":0.14445332,"width":0.056848403,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"concurrently, they can both pass the","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"if (! is_null(Redis::get(...)))","depth":27,"bounds":{"left":0.18351063,"top":0.16360734,"width":0.09275266,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"check at the exact same millisecond. Both will then execute","depth":26,"bounds":{"left":0.12450133,"top":0.16201118,"width":0.21575798,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.2122673,"top":0.18276137,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and push duplicate metrics to Datadog.","depth":26,"bounds":{"left":0.22922207,"top":0.1811652,"width":0.09823803,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Fix:","depth":26,"bounds":{"left":0.12450133,"top":0.20989625,"width":0.019281914,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This should be replaced with a single atomic operation using Redis's","depth":26,"bounds":{"left":0.14378324,"top":0.20989625,"width":0.16821809,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"NX","depth":27,"bounds":{"left":0.31399602,"top":0.21149242,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Not eXists) and","depth":26,"bounds":{"left":0.12450133,"top":0.20989625,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"EX","depth":27,"bounds":{"left":0.13696809,"top":0.23064645,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Expire) flags, or Laravel’s built-in atomic cache locks:","depth":26,"bounds":{"left":0.14494681,"top":0.22905028,"width":0.13264628,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis::set($gaugeLockName, true, 'EX', 60, 'NX')","depth":27,"bounds":{"left":0.12649602,"top":0.24980047,"width":0.14361702,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.27210772,"top":0.2482043,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"3. Infinite Loops and Caller Contract","depth":23,"bounds":{"left":0.111369684,"top":0.2980846,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. Infinite Loops and Caller Contract","depth":24,"bounds":{"left":0.111369684,"top":0.29768556,"width":0.10571808,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Return Value Change:","depth":26,"bounds":{"left":0.12450133,"top":0.32482043,"width":0.055352394,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"bounds":{"left":0.18301196,"top":0.3264166,"width":0.06881649,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"previously returned whether it should iterate again based on staleness or chunk size. It now always returns","depth":26,"bounds":{"left":0.12450133,"top":0.32482043,"width":0.2237367,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"bounds":{"left":0.29438165,"top":0.34557062,"width":0.011968086,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(unless the selection is totally empty).","depth":26,"bounds":{"left":0.12450133,"top":0.34397447,"width":0.2130984,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.39185953,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the code calling this method relies on a","depth":26,"bounds":{"left":0.16456117,"top":0.39185953,"width":0.10305851,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"false","depth":27,"bounds":{"left":0.26961437,"top":0.3934557,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"return to pause, sleep, or gracefully terminate, changing this to an unconditional","depth":26,"bounds":{"left":0.12450133,"top":0.39185953,"width":0.22589761,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"bounds":{"left":0.26163563,"top":0.41260973,"width":0.011968086,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"could result in an aggressive \"busy loop\" that spikes CPU usage.","depth":26,"bounds":{"left":0.12450133,"top":0.41101357,"width":0.22290559,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"4. Performance Overhead","depth":23,"bounds":{"left":0.111369684,"top":0.48004788,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4. Performance Overhead","depth":24,"bounds":{"left":0.111369684,"top":0.47964883,"width":0.076130316,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Expensive Garbage Collection:","depth":26,"bounds":{"left":0.12450133,"top":0.5067837,"width":0.078457445,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Calling","depth":26,"bounds":{"left":0.20295878,"top":0.5067837,"width":0.019281914,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles();","depth":27,"bounds":{"left":0.22423537,"top":0.5083799,"width":0.059840426,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"on","depth":26,"bounds":{"left":0.28607047,"top":0.5067837,"width":0.00880984,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"every single chunk","depth":26,"bounds":{"left":0.29488033,"top":0.5067837,"width":0.045545213,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.","depth":26,"bounds":{"left":0.12450133,"top":0.5067837,"width":0.23420878,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Synchronous Metric Dispatch:","depth":26,"bounds":{"left":0.12450133,"top":0.5738228,"width":0.07712766,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the","depth":26,"bounds":{"left":0.20162898,"top":0.5738228,"width":0.015292553,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Datadog::increment","depth":27,"bounds":{"left":0.21891622,"top":0.575419,"width":0.053856384,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"or","depth":26,"bounds":{"left":0.27476728,"top":0.5738228,"width":0.0078125,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Datadog::gauge","depth":27,"bounds":{"left":0.28457448,"top":0.575419,"width":0.041888297,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.","depth":26,"bounds":{"left":0.12450133,"top":0.5738228,"width":0.23420878,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"5. Lack of Explicit Error Handling","depth":23,"bounds":{"left":0.111369684,"top":0.66201115,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5. Lack of Explicit Error Handling","depth":24,"bounds":{"left":0.111369684,"top":0.66161215,"width":0.094913565,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No","depth":26,"bounds":{"left":0.12450133,"top":0.688747,"width":0.008477394,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"try/catch","depth":27,"bounds":{"left":0.1349734,"top":0.6903432,"width":0.026928192,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Blocks:","depth":26,"bounds":{"left":0.16389628,"top":0.688747,"width":0.019614361,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The script executes complex network operations (","depth":26,"bounds":{"left":0.18351063,"top":0.688747,"width":0.12367021,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"loadDocuments","depth":27,"bounds":{"left":0.30917552,"top":0.6903432,"width":0.038896278,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.","depth":26,"bounds":{"left":0.12450133,"top":0.688747,"width":0.22888963,"height":0.09377494},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":22,"bounds":{"left":0.1087101,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":22,"bounds":{"left":0.124667555,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Redo","depth":22,"bounds":{"left":0.140625,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":21,"bounds":{"left":0.15658244,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":22,"bounds":{"left":0.17253989,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":21,"bounds":{"left":0.18849733,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXTextArea","text":"Ask Gemini","depth":21,"bounds":{"left":0.14594415,"top":0.86632085,"width":0.17021276,"height":0.01915403},"on_screen":true,"value":"Ask Gemini","help_text":"","role_description":"text entry area","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Ask Gemini","depth":22,"bounds":{"left":0.14594415,"top":0.8667199,"width":0.030418882,"height":0.018355945},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Upload & tools","depth":20,"bounds":{"left":0.12732713,"top":0.8567438,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Microphone","depth":20,"bounds":{"left":0.32413563,"top":0.86153233,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Send message","depth":20,"bounds":{"left":0.33211437,"top":0.8631285,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Your Jiminny chats aren’t used to improve our models. Gemini is AI. It can make mistakes, so double check it.","depth":17,"bounds":{"left":0.102726065,"top":0.9173983,"width":0.21775267,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Your privacy & Gemini Opens in a new window","depth":17,"bounds":{"left":0.32047874,"top":0.9173983,"width":0.043882977,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Your privacy & Gemini","depth":18,"bounds":{"left":0.32047874,"top":0.9173983,"width":0.043882977,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Opens in a new window","depth":19,"bounds":{"left":0.079288565,"top":0.91660017,"width":0.04720745,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Summarize page","depth":7,"bounds":{"left":0.08494016,"top":0.95730245,"width":0.053523935,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Summarize page","depth":9,"bounds":{"left":0.09059176,"top":0.96249,"width":0.042220745,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Skip to content","depth":6,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to content","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open menu","depth":10,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Homepage (g then d)","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"jiminny","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"jiminny","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"app","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"app","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Search or jump to…","depth":9,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Type","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to search","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Chat with Copilot","depth":10,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Open Copilot…","depth":9,"on_screen":false,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Create new...","depth":9,"on_screen":false,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"All issues(g then i)","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"All pull requests","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"All repositories","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"You have unread notifications(g then n)","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open user navigation menu","depth":9,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Repository navigation","depth":9,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Repository navigation","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Code","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Code","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Pull requests (31)","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pull requests","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"31","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Agents","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Agents","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Actions","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Actions","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Wiki","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Wiki","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Security and quality","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Security and quality","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Insights","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Insights","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Settings","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Settings","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"JY-20676 delete AJ reports related objects #12098 Edit title","depth":13,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"#","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"12098","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Edit title","depth":14,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Checks pending","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Checks pending","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Code","depth":13,"on_screen":false,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Code","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Open","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-6189565768846781858
|
-1070810957936625892
|
visual_change
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage.
4. Performance Overhead
4. Performance Overhead
Expensive Garbage Collection:
Calling
gc_collect_cycles();
on
every single chunk
can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.
Synchronous Metric Dispatch:
If the
Datadog::increment
or
Datadog::gauge
methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.
5. Lack of Explicit Error Handling
5. Lack of Explicit Error Handling
No
try/catch
Blocks:
The script executes complex network operations (
loadDocuments
, Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.
Good response
Bad response
Redo
Share & export
Copy
Show more options
Ask Gemini
Ask Gemini
Upload & tools
Microphone
Send message
Your Jiminny chats aren’t used to improve our models. Gemini is AI. It can make mistakes, so double check it.
Your privacy & Gemini Opens in a new window
Your privacy & Gemini
Opens in a new window
Summarize page
Summarize page
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
app
app
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
All issues(g then i)
All pull requests
All repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Pull requests (31)
Pull requests
(
31
)
Agents
Agents
Actions
Actions
Wiki
Wiki
Security and quality
Security and quality
Insights
Insights
Settings
Settings
JY-20676 delete AJ reports related objects #12098 Edit title
JY-20676 delete AJ reports related objects
#
12098
Edit title
Checks pending
Checks pending
Code
Code
Open...
|
64696
|
NULL
|
NULL
|
NULL
|
|
64698
|
2266
|
1
|
2026-05-20T14:14:05.465634+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779286445465_m1.jpg...
|
Firefox
|
JY-20676 delete AJ reports related objects by Laky JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app — Work...
|
1
|
github.com/jiminny/app/pull/12098
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Main menu","depth":13,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New chat","depth":12,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Chat","depth":13,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":13,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"You said I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
4344092198535999235
|
-1026409384166083752
|
idle
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
64699
|
2267
|
2
|
2026-05-20T14:14:08.343878+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779286448343_m2.jpg...
|
Firefox
|
JY-20676 delete AJ reports related objects by Laky JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app — Work...
|
1
|
github.com/jiminny/app/pull/12098
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage.
4. Performance Overhead
4. Performance Overhead
Expensive Garbage Collection:
Calling
gc_collect_cycles();
on
every single chunk
can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.
Synchronous Metric Dispatch:
If the
Datadog::increment
or
Datadog::gauge
methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.
5. Lack of Explicit Error Handling...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.10721409,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.15791224,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.1245012,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.1796875,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.12699468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":4,"bounds":{"left":0.0,"top":0.21548285,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":5,"bounds":{"left":0.013297873,"top":0.22665602,"width":0.10139628,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"bounds":{"left":0.0,"top":0.2482043,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.25937748,"width":0.18068483,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.28092578,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.29209897,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.31364724,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.32482043,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.3463687,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.3575419,"width":0.15824468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.38068634,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"bounds":{"left":0.35854387,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"bounds":{"left":0.37051198,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Main menu","depth":13,"bounds":{"left":0.08228058,"top":0.09736632,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New chat","depth":12,"bounds":{"left":0.09823803,"top":0.10215483,"width":0.028091755,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":15,"bounds":{"left":0.1008976,"top":0.10654429,"width":0.02144282,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Chat","depth":13,"bounds":{"left":0.35854387,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":13,"bounds":{"left":0.37051198,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"bounds":{"left":0.079288565,"top":0.14445332,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"bounds":{"left":0.079288565,"top":0.14684756,"width":0.1200133,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"You said I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a concise summary of the changes made in the code selection:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Refactoring and Dependencies","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Refactoring and Dependencies","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Replaced Interface with Implementation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusInterface","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusManager","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in the imports and constructor dependency injection.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Redis Support:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Imported","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Illuminate\\Support\\Facades\\Redis","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to handle a newly introduced memory-logging lock mechanism.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Traits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WorkerIdTrait","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", along with the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"worker","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"field tracking in the final logs.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Loop and Worker Lifecycle Adjustments","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Loop and Worker Lifecycle Adjustments","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Worker Auto-Restart:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Deleted the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property, the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"constant, and the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"shouldIterateAgain()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method. The script no longer tracks execution iterations or triggers a self-managed worker restart.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Simplified Chunk Method Return:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Modified","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to always return","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Priority Handling for Throttled Entities","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Priority Handling for Throttled Entities","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Stale Record Elevation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Updated the logic in","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"rescheduleThrottledEntities()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to check if a throttled entity group is classified as \"stale\". If it is, the entities are rescheduled using","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"QueuePriorityEnum::NORMAL","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"; otherwise, they keep their respective queue priority.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Metrics, Logging, and Environment Handling","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Metrics, Logging, and Environment Handling","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Environment Tracking:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Introduced a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$environment","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property (defaulting to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'local'","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") and a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"setEnvironment()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method to dynamically assign it.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Memory Monitoring via Datadog Gauge:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method that submits peak memory allocation to Datadog as a gauge metric (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"jiminny.reindex-memory-usage","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis Lock for Metrics Throttling:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Implemented a Redis-backed lock (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"%s-process-manager-lock","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") with a 60-second expiration inside","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to prevent over-reporting metrics.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Log Payload Update:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added total processed items (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$updatedDocuments + $deletedDocuments","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") to the final information log.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":23,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said are there any issues or potential bottlenecks","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"are there any issues or potential bottlenecks","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a breakdown of the potential issues to look out for:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"1. Memory Leak Risk (OOM Crashes)","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. Memory Leak Risk (OOM Crashes)","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Restart Logic:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The most significant change is the removal of the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Dependency Leaks:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If any underlying service (like the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ElasticSearchClient","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", ORM, or Datadog helper) retains references to processed entities,","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"bounds":{"left":0.26811835,"top":0.0,"width":0.056848403,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23138298,"height":0.05546688},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Hardcoded Limits:","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.047041222,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The code still includes","depth":26,"bounds":{"left":0.17154256,"top":0.0,"width":0.05651596,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ini_set('memory_limit', '512M');","depth":27,"bounds":{"left":0.23005319,"top":0.0,"width":0.095744684,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23055187,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"2. Race Condition in Redis Lock","depth":23,"bounds":{"left":0.111369684,"top":0.06823623,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. Race Condition in Redis Lock","depth":24,"bounds":{"left":0.111369684,"top":0.06783719,"width":0.09125665,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Non-Atomic Operations:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.062333778,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The","depth":26,"bounds":{"left":0.18683511,"top":0.09497207,"width":0.011635638,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage","depth":27,"bounds":{"left":0.20046543,"top":0.096568234,"width":0.041888297,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method uses three distinct steps to handle the lock:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.22722739,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"get()","depth":27,"bounds":{"left":0.14860372,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":26,"bounds":{"left":0.16555852,"top":0.11412609,"width":0.002493351,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.17004654,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", and","depth":26,"bounds":{"left":0.18700133,"top":0.11412609,"width":0.013131649,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"expire()","depth":27,"bounds":{"left":0.20212767,"top":0.11572227,"width":0.023936171,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.22805852,"top":0.11412609,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If multiple workers process the same","depth":26,"bounds":{"left":0.16456117,"top":0.14285715,"width":0.09275266,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$this->updateTarget","depth":27,"bounds":{"left":0.25930852,"top":0.14445332,"width":0.056848403,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"concurrently, they can both pass the","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"if (! is_null(Redis::get(...)))","depth":27,"bounds":{"left":0.18351063,"top":0.16360734,"width":0.09275266,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"check at the exact same millisecond. Both will then execute","depth":26,"bounds":{"left":0.12450133,"top":0.16201118,"width":0.21575798,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.2122673,"top":0.18276137,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and push duplicate metrics to Datadog.","depth":26,"bounds":{"left":0.22922207,"top":0.1811652,"width":0.09823803,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Fix:","depth":26,"bounds":{"left":0.12450133,"top":0.20989625,"width":0.019281914,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This should be replaced with a single atomic operation using Redis's","depth":26,"bounds":{"left":0.14378324,"top":0.20989625,"width":0.16821809,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"NX","depth":27,"bounds":{"left":0.31399602,"top":0.21149242,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Not eXists) and","depth":26,"bounds":{"left":0.12450133,"top":0.20989625,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"EX","depth":27,"bounds":{"left":0.13696809,"top":0.23064645,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Expire) flags, or Laravel’s built-in atomic cache locks:","depth":26,"bounds":{"left":0.14494681,"top":0.22905028,"width":0.13264628,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis::set($gaugeLockName, true, 'EX', 60, 'NX')","depth":27,"bounds":{"left":0.12649602,"top":0.24980047,"width":0.14361702,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.27210772,"top":0.2482043,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"3. Infinite Loops and Caller Contract","depth":23,"bounds":{"left":0.111369684,"top":0.2980846,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. Infinite Loops and Caller Contract","depth":24,"bounds":{"left":0.111369684,"top":0.29768556,"width":0.10571808,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Return Value Change:","depth":26,"bounds":{"left":0.12450133,"top":0.32482043,"width":0.055352394,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"bounds":{"left":0.18301196,"top":0.3264166,"width":0.06881649,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"previously returned whether it should iterate again based on staleness or chunk size. It now always returns","depth":26,"bounds":{"left":0.12450133,"top":0.32482043,"width":0.2237367,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"bounds":{"left":0.29438165,"top":0.34557062,"width":0.011968086,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(unless the selection is totally empty).","depth":26,"bounds":{"left":0.12450133,"top":0.34397447,"width":0.2130984,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.39185953,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the code calling this method relies on a","depth":26,"bounds":{"left":0.16456117,"top":0.39185953,"width":0.10305851,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"false","depth":27,"bounds":{"left":0.26961437,"top":0.3934557,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"return to pause, sleep, or gracefully terminate, changing this to an unconditional","depth":26,"bounds":{"left":0.12450133,"top":0.39185953,"width":0.22589761,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"bounds":{"left":0.26163563,"top":0.41260973,"width":0.011968086,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"could result in an aggressive \"busy loop\" that spikes CPU usage.","depth":26,"bounds":{"left":0.12450133,"top":0.41101357,"width":0.22290559,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"4. Performance Overhead","depth":23,"bounds":{"left":0.111369684,"top":0.48004788,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4. Performance Overhead","depth":24,"bounds":{"left":0.111369684,"top":0.47964883,"width":0.076130316,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Expensive Garbage Collection:","depth":26,"bounds":{"left":0.12450133,"top":0.5067837,"width":0.078457445,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Calling","depth":26,"bounds":{"left":0.20295878,"top":0.5067837,"width":0.019281914,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles();","depth":27,"bounds":{"left":0.22423537,"top":0.5083799,"width":0.059840426,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"on","depth":26,"bounds":{"left":0.28607047,"top":0.5067837,"width":0.00880984,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"every single chunk","depth":26,"bounds":{"left":0.29488033,"top":0.5067837,"width":0.045545213,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.","depth":26,"bounds":{"left":0.12450133,"top":0.5067837,"width":0.23420878,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Synchronous Metric Dispatch:","depth":26,"bounds":{"left":0.12450133,"top":0.5738228,"width":0.07712766,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the","depth":26,"bounds":{"left":0.20162898,"top":0.5738228,"width":0.015292553,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Datadog::increment","depth":27,"bounds":{"left":0.21891622,"top":0.575419,"width":0.053856384,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"or","depth":26,"bounds":{"left":0.27476728,"top":0.5738228,"width":0.0078125,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Datadog::gauge","depth":27,"bounds":{"left":0.28457448,"top":0.575419,"width":0.041888297,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.","depth":26,"bounds":{"left":0.12450133,"top":0.5738228,"width":0.23420878,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"5. Lack of Explicit Error Handling","depth":23,"bounds":{"left":0.111369684,"top":0.66201115,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"}]...
|
988148017449334202
|
-1079959444431538212
|
visual_change
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage.
4. Performance Overhead
4. Performance Overhead
Expensive Garbage Collection:
Calling
gc_collect_cycles();
on
every single chunk
can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.
Synchronous Metric Dispatch:
If the
Datadog::increment
or
Datadog::gauge
methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.
5. Lack of Explicit Error Handling...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
64700
|
2267
|
3
|
2026-05-20T14:14:11.359131+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779286451359_m2.jpg...
|
Firefox
|
JY-20676 delete AJ reports related objects by Laky JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app — Work...
|
1
|
github.com/jiminny/app/pull/12098
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage.
4. Performance Overhead
4. Performance Overhead
Expensive Garbage Collection:
Calling
gc_collect_cycles();
on
every single chunk
can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.
Synchronous Metric Dispatch:
If the
Datadog::increment
or
Datadog::gauge
methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.
5. Lack of Explicit Error Handling
5. Lack of Explicit Error Handling
No
try/catch
Blocks:
The script executes complex network operations (
loadDocuments
, Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.
Good response
Bad response
Redo
Share & export
Copy
Show more options
Ask Gemini
Ask Gemini
Upload & tools
Microphone
Send message
Your Jiminny chats aren’t used to improve our models. Gemini is AI. It can make mistakes, so double check it.
Your privacy & Gemini Opens in a new window
Your privacy & Gemini
Opens in a new window
Summarize page
Summarize page
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
app
app
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
All issues(g then i)
All pull requests
All repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Pull requests (31)
Pull requests
(
31
)
Agents
Agents
Actions
Actions
Wiki
Wiki
Security and quality
Security and quality
Insights
Insights
Settings
Settings
JY-20676 delete AJ reports related objects #12098 Edit title
JY-20676 delete AJ reports related objects
#
12098
Edit title
Checks pending
Checks pending
Code
Code
Open
LakyLak
LakyLak
wants to merge 20 commits into
master
master
from
JY-20676-delete-report-related-objects
JY-20676-delete-report-related-objects
Copy head branch name to clipboard
Lines changed: 1698 additions & 1471 deletions
Conversation (9)
Conversation
(
9
)...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.10721409,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.15791224,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.1245012,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.1796875,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.12699468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":4,"bounds":{"left":0.0,"top":0.21548285,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":5,"bounds":{"left":0.013297873,"top":0.22665602,"width":0.10139628,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"bounds":{"left":0.0,"top":0.2482043,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.25937748,"width":0.18068483,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.28092578,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.29209897,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.31364724,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.32482043,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.3463687,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.3575419,"width":0.15824468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.38068634,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"bounds":{"left":0.35854387,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"bounds":{"left":0.37051198,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Main menu","depth":13,"bounds":{"left":0.08228058,"top":0.09736632,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New chat","depth":12,"bounds":{"left":0.09823803,"top":0.10215483,"width":0.028091755,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":15,"bounds":{"left":0.1008976,"top":0.10654429,"width":0.02144282,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Chat","depth":13,"bounds":{"left":0.35854387,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":13,"bounds":{"left":0.37051198,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"bounds":{"left":0.079288565,"top":0.14445332,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"bounds":{"left":0.079288565,"top":0.14684756,"width":0.1200133,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"You said I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a concise summary of the changes made in the code selection:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Refactoring and Dependencies","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Refactoring and Dependencies","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Replaced Interface with Implementation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusInterface","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusManager","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in the imports and constructor dependency injection.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Redis Support:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Imported","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Illuminate\\Support\\Facades\\Redis","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to handle a newly introduced memory-logging lock mechanism.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Traits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WorkerIdTrait","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", along with the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"worker","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"field tracking in the final logs.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Loop and Worker Lifecycle Adjustments","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Loop and Worker Lifecycle Adjustments","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Worker Auto-Restart:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Deleted the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property, the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"constant, and the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"shouldIterateAgain()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method. The script no longer tracks execution iterations or triggers a self-managed worker restart.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Simplified Chunk Method Return:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Modified","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to always return","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Priority Handling for Throttled Entities","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Priority Handling for Throttled Entities","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Stale Record Elevation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Updated the logic in","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"rescheduleThrottledEntities()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to check if a throttled entity group is classified as \"stale\". If it is, the entities are rescheduled using","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"QueuePriorityEnum::NORMAL","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"; otherwise, they keep their respective queue priority.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Metrics, Logging, and Environment Handling","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Metrics, Logging, and Environment Handling","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Environment Tracking:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Introduced a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$environment","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property (defaulting to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'local'","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") and a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"setEnvironment()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method to dynamically assign it.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Memory Monitoring via Datadog Gauge:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method that submits peak memory allocation to Datadog as a gauge metric (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"jiminny.reindex-memory-usage","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis Lock for Metrics Throttling:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Implemented a Redis-backed lock (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"%s-process-manager-lock","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") with a 60-second expiration inside","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to prevent over-reporting metrics.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Log Payload Update:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added total processed items (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$updatedDocuments + $deletedDocuments","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") to the final information log.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":23,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said are there any issues or potential bottlenecks","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"are there any issues or potential bottlenecks","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a breakdown of the potential issues to look out for:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"1. Memory Leak Risk (OOM Crashes)","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. Memory Leak Risk (OOM Crashes)","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Restart Logic:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The most significant change is the removal of the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Dependency Leaks:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If any underlying service (like the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ElasticSearchClient","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", ORM, or Datadog helper) retains references to processed entities,","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"bounds":{"left":0.26811835,"top":0.0,"width":0.056848403,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23138298,"height":0.05546688},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Hardcoded Limits:","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.047041222,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The code still includes","depth":26,"bounds":{"left":0.17154256,"top":0.0,"width":0.05651596,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ini_set('memory_limit', '512M');","depth":27,"bounds":{"left":0.23005319,"top":0.0,"width":0.095744684,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23055187,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"2. Race Condition in Redis Lock","depth":23,"bounds":{"left":0.111369684,"top":0.06823623,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. Race Condition in Redis Lock","depth":24,"bounds":{"left":0.111369684,"top":0.06783719,"width":0.09125665,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Non-Atomic Operations:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.062333778,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The","depth":26,"bounds":{"left":0.18683511,"top":0.09497207,"width":0.011635638,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage","depth":27,"bounds":{"left":0.20046543,"top":0.096568234,"width":0.041888297,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method uses three distinct steps to handle the lock:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.22722739,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"get()","depth":27,"bounds":{"left":0.14860372,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":26,"bounds":{"left":0.16555852,"top":0.11412609,"width":0.002493351,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.17004654,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", and","depth":26,"bounds":{"left":0.18700133,"top":0.11412609,"width":0.013131649,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"expire()","depth":27,"bounds":{"left":0.20212767,"top":0.11572227,"width":0.023936171,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.22805852,"top":0.11412609,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If multiple workers process the same","depth":26,"bounds":{"left":0.16456117,"top":0.14285715,"width":0.09275266,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$this->updateTarget","depth":27,"bounds":{"left":0.25930852,"top":0.14445332,"width":0.056848403,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"concurrently, they can both pass the","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"if (! is_null(Redis::get(...)))","depth":27,"bounds":{"left":0.18351063,"top":0.16360734,"width":0.09275266,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"check at the exact same millisecond. Both will then execute","depth":26,"bounds":{"left":0.12450133,"top":0.16201118,"width":0.21575798,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.2122673,"top":0.18276137,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and push duplicate metrics to Datadog.","depth":26,"bounds":{"left":0.22922207,"top":0.1811652,"width":0.09823803,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Fix:","depth":26,"bounds":{"left":0.12450133,"top":0.20989625,"width":0.019281914,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This should be replaced with a single atomic operation using Redis's","depth":26,"bounds":{"left":0.14378324,"top":0.20989625,"width":0.16821809,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"NX","depth":27,"bounds":{"left":0.31399602,"top":0.21149242,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Not eXists) and","depth":26,"bounds":{"left":0.12450133,"top":0.20989625,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"EX","depth":27,"bounds":{"left":0.13696809,"top":0.23064645,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Expire) flags, or Laravel’s built-in atomic cache locks:","depth":26,"bounds":{"left":0.14494681,"top":0.22905028,"width":0.13264628,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis::set($gaugeLockName, true, 'EX', 60, 'NX')","depth":27,"bounds":{"left":0.12649602,"top":0.24980047,"width":0.14361702,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.27210772,"top":0.2482043,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"3. Infinite Loops and Caller Contract","depth":23,"bounds":{"left":0.111369684,"top":0.2980846,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. Infinite Loops and Caller Contract","depth":24,"bounds":{"left":0.111369684,"top":0.29768556,"width":0.10571808,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Return Value Change:","depth":26,"bounds":{"left":0.12450133,"top":0.32482043,"width":0.055352394,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"bounds":{"left":0.18301196,"top":0.3264166,"width":0.06881649,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"previously returned whether it should iterate again based on staleness or chunk size. It now always returns","depth":26,"bounds":{"left":0.12450133,"top":0.32482043,"width":0.2237367,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"bounds":{"left":0.29438165,"top":0.34557062,"width":0.011968086,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(unless the selection is totally empty).","depth":26,"bounds":{"left":0.12450133,"top":0.34397447,"width":0.2130984,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.39185953,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the code calling this method relies on a","depth":26,"bounds":{"left":0.16456117,"top":0.39185953,"width":0.10305851,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"false","depth":27,"bounds":{"left":0.26961437,"top":0.3934557,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"return to pause, sleep, or gracefully terminate, changing this to an unconditional","depth":26,"bounds":{"left":0.12450133,"top":0.39185953,"width":0.22589761,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"bounds":{"left":0.26163563,"top":0.41260973,"width":0.011968086,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"could result in an aggressive \"busy loop\" that spikes CPU usage.","depth":26,"bounds":{"left":0.12450133,"top":0.41101357,"width":0.22290559,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"4. Performance Overhead","depth":23,"bounds":{"left":0.111369684,"top":0.48004788,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4. Performance Overhead","depth":24,"bounds":{"left":0.111369684,"top":0.47964883,"width":0.076130316,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Expensive Garbage Collection:","depth":26,"bounds":{"left":0.12450133,"top":0.5067837,"width":0.078457445,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Calling","depth":26,"bounds":{"left":0.20295878,"top":0.5067837,"width":0.019281914,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles();","depth":27,"bounds":{"left":0.22423537,"top":0.5083799,"width":0.059840426,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"on","depth":26,"bounds":{"left":0.28607047,"top":0.5067837,"width":0.00880984,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"every single chunk","depth":26,"bounds":{"left":0.29488033,"top":0.5067837,"width":0.045545213,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.","depth":26,"bounds":{"left":0.12450133,"top":0.5067837,"width":0.23420878,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Synchronous Metric Dispatch:","depth":26,"bounds":{"left":0.12450133,"top":0.5738228,"width":0.07712766,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the","depth":26,"bounds":{"left":0.20162898,"top":0.5738228,"width":0.015292553,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Datadog::increment","depth":27,"bounds":{"left":0.21891622,"top":0.575419,"width":0.053856384,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"or","depth":26,"bounds":{"left":0.27476728,"top":0.5738228,"width":0.0078125,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Datadog::gauge","depth":27,"bounds":{"left":0.28457448,"top":0.575419,"width":0.041888297,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.","depth":26,"bounds":{"left":0.12450133,"top":0.5738228,"width":0.23420878,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"5. Lack of Explicit Error Handling","depth":23,"bounds":{"left":0.111369684,"top":0.66201115,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5. Lack of Explicit Error Handling","depth":24,"bounds":{"left":0.111369684,"top":0.66161215,"width":0.094913565,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No","depth":26,"bounds":{"left":0.12450133,"top":0.688747,"width":0.008477394,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"try/catch","depth":27,"bounds":{"left":0.1349734,"top":0.6903432,"width":0.026928192,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Blocks:","depth":26,"bounds":{"left":0.16389628,"top":0.688747,"width":0.019614361,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The script executes complex network operations (","depth":26,"bounds":{"left":0.18351063,"top":0.688747,"width":0.12367021,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"loadDocuments","depth":27,"bounds":{"left":0.30917552,"top":0.6903432,"width":0.038896278,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.","depth":26,"bounds":{"left":0.12450133,"top":0.688747,"width":0.22888963,"height":0.09377494},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":22,"bounds":{"left":0.1087101,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":22,"bounds":{"left":0.124667555,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Redo","depth":22,"bounds":{"left":0.140625,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":21,"bounds":{"left":0.15658244,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":22,"bounds":{"left":0.17253989,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":21,"bounds":{"left":0.18849733,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXTextArea","text":"Ask Gemini","depth":21,"bounds":{"left":0.14594415,"top":0.86632085,"width":0.17021276,"height":0.01915403},"on_screen":true,"value":"Ask Gemini","help_text":"","role_description":"text entry area","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Ask Gemini","depth":22,"bounds":{"left":0.14594415,"top":0.8667199,"width":0.030418882,"height":0.018355945},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Upload & tools","depth":20,"bounds":{"left":0.12732713,"top":0.8567438,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Microphone","depth":20,"bounds":{"left":0.32413563,"top":0.86153233,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Send message","depth":20,"bounds":{"left":0.33211437,"top":0.8631285,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Your Jiminny chats aren’t used to improve our models. Gemini is AI. It can make mistakes, so double check it.","depth":17,"bounds":{"left":0.102726065,"top":0.9173983,"width":0.21775267,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Your privacy & Gemini Opens in a new window","depth":17,"bounds":{"left":0.32047874,"top":0.9173983,"width":0.043882977,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Your privacy & Gemini","depth":18,"bounds":{"left":0.32047874,"top":0.9173983,"width":0.043882977,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Opens in a new window","depth":19,"bounds":{"left":0.079288565,"top":0.91660017,"width":0.04720745,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Summarize page","depth":7,"bounds":{"left":0.08494016,"top":0.95730245,"width":0.053523935,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Summarize page","depth":9,"bounds":{"left":0.09059176,"top":0.96249,"width":0.042220745,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Skip to content","depth":6,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to content","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open menu","depth":10,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Homepage (g then d)","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"jiminny","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"jiminny","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"app","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"app","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Search or jump to…","depth":9,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Type","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to search","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Chat with Copilot","depth":10,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Open Copilot…","depth":9,"on_screen":false,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Create new...","depth":9,"on_screen":false,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"All issues(g then i)","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"All pull requests","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"All repositories","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"You have unread notifications(g then n)","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open user navigation menu","depth":9,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Repository navigation","depth":9,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Repository navigation","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Code","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Code","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Pull requests (31)","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pull requests","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"31","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Agents","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Agents","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Actions","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Actions","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Wiki","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Wiki","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Security and quality","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Security and quality","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Insights","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Insights","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Settings","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Settings","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"JY-20676 delete AJ reports related objects #12098 Edit title","depth":13,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"#","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"12098","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Edit title","depth":14,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Checks pending","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Checks pending","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Code","depth":13,"on_screen":false,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Code","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Open","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"LakyLak","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"LakyLak","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"wants to merge 20 commits into","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"master","depth":15,"on_screen":false,"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"master","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"from","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"JY-20676-delete-report-related-objects","depth":16,"on_screen":false,"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20676-delete-report-related-objects","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy head branch name to clipboard","depth":16,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Lines changed: 1698 additions & 1471 deletions","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Conversation (9)","depth":16,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Conversation","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"9","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-7087746738428694851
|
-1052787213578504420
|
visual_change
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage.
4. Performance Overhead
4. Performance Overhead
Expensive Garbage Collection:
Calling
gc_collect_cycles();
on
every single chunk
can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.
Synchronous Metric Dispatch:
If the
Datadog::increment
or
Datadog::gauge
methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.
5. Lack of Explicit Error Handling
5. Lack of Explicit Error Handling
No
try/catch
Blocks:
The script executes complex network operations (
loadDocuments
, Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.
Good response
Bad response
Redo
Share & export
Copy
Show more options
Ask Gemini
Ask Gemini
Upload & tools
Microphone
Send message
Your Jiminny chats aren’t used to improve our models. Gemini is AI. It can make mistakes, so double check it.
Your privacy & Gemini Opens in a new window
Your privacy & Gemini
Opens in a new window
Summarize page
Summarize page
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
app
app
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
All issues(g then i)
All pull requests
All repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Pull requests (31)
Pull requests
(
31
)
Agents
Agents
Actions
Actions
Wiki
Wiki
Security and quality
Security and quality
Insights
Insights
Settings
Settings
JY-20676 delete AJ reports related objects #12098 Edit title
JY-20676 delete AJ reports related objects
#
12098
Edit title
Checks pending
Checks pending
Code
Code
Open
LakyLak
LakyLak
wants to merge 20 commits into
master
master
from
JY-20676-delete-report-related-objects
JY-20676-delete-report-related-objects
Copy head branch name to clipboard
Lines changed: 1698 additions & 1471 deletions
Conversation (9)
Conversation
(
9
)...
|
64699
|
NULL
|
NULL
|
NULL
|
|
64704
|
2267
|
5
|
2026-05-20T14:14:32.132320+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779286472132_m2.jpg...
|
Firefox
|
Service-Desk - Queues - Platform team - Service sp Service-Desk - Queues - Platform team - Service space - Jira — Work...
|
1
|
jiminny.atlassian.net/jira/software/c/projects/JY/ jiminny.atlassian.net/jira/software/c/projects/JY/boards/37...
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Close tab
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Close tab
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.05905826,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.10721409,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.09177973,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.15791224,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.1796875,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.12699468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":4,"bounds":{"left":0.0,"top":0.21548285,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":5,"bounds":{"left":0.013297873,"top":0.22665602,"width":0.10139628,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"bounds":{"left":0.0,"top":0.2482043,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.25937748,"width":0.18068483,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.28092578,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.29209897,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.31364724,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.32482043,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.3463687,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.3575419,"width":0.15824468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.38068634,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"bounds":{"left":0.35854387,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"bounds":{"left":0.37051198,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Main menu","depth":13,"bounds":{"left":0.08228058,"top":0.09736632,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New chat","depth":12,"bounds":{"left":0.09823803,"top":0.10215483,"width":0.028091755,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":15,"bounds":{"left":0.1008976,"top":0.10654429,"width":0.02144282,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Chat","depth":13,"bounds":{"left":0.35854387,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":13,"bounds":{"left":0.37051198,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"bounds":{"left":0.079288565,"top":0.14445332,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"bounds":{"left":0.079288565,"top":0.14684756,"width":0.1200133,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"You said I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a concise summary of the changes made in the code selection:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Refactoring and Dependencies","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Refactoring and Dependencies","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Replaced Interface with Implementation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusInterface","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusManager","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in the imports and constructor dependency injection.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Redis Support:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Imported","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Illuminate\\Support\\Facades\\Redis","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to handle a newly introduced memory-logging lock mechanism.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Traits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WorkerIdTrait","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", along with the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"worker","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"field tracking in the final logs.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Loop and Worker Lifecycle Adjustments","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Loop and Worker Lifecycle Adjustments","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Worker Auto-Restart:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Deleted the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property, the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"constant, and the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"shouldIterateAgain()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method. The script no longer tracks execution iterations or triggers a self-managed worker restart.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Simplified Chunk Method Return:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Modified","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to always return","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Priority Handling for Throttled Entities","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Priority Handling for Throttled Entities","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Stale Record Elevation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Updated the logic in","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"rescheduleThrottledEntities()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to check if a throttled entity group is classified as \"stale\". If it is, the entities are rescheduled using","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"QueuePriorityEnum::NORMAL","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"; otherwise, they keep their respective queue priority.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Metrics, Logging, and Environment Handling","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Metrics, Logging, and Environment Handling","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Environment Tracking:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Introduced a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$environment","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property (defaulting to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'local'","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") and a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"setEnvironment()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method to dynamically assign it.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Memory Monitoring via Datadog Gauge:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method that submits peak memory allocation to Datadog as a gauge metric (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"jiminny.reindex-memory-usage","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis Lock for Metrics Throttling:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Implemented a Redis-backed lock (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"%s-process-manager-lock","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") with a 60-second expiration inside","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to prevent over-reporting metrics.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Log Payload Update:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added total processed items (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$updatedDocuments + $deletedDocuments","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") to the final information log.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":23,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said are there any issues or potential bottlenecks","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"are there any issues or potential bottlenecks","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
8419994565776980507
|
-881731280493578340
|
click
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Close tab
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Close tab
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt...
|
64702
|
NULL
|
NULL
|
NULL
|
|
64705
|
2266
|
4
|
2026-05-20T14:14:31.332339+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779286471332_m1.jpg...
|
Firefox
|
Service-Desk - Queues - Platform team - Service sp Service-Desk - Queues - Platform team - Service space - Jira — Work...
|
1
|
jiminny.atlassian.net/jira/servicedesk/projects/SR jiminny.atlassian.net/jira/servicedesk/projects/SRD/queues/custom/37...
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
SlackFileEditViewGoHistoryWindowHelpAPPDOCKER₴81DE SlackFileEditViewGoHistoryWindowHelpAPPDOCKER₴81DEV (-zsh)₴2Fixed 0 of 5690 files in 78.144 seconds, 60.00 MBmemory usedWhat's next:Try Docker Debug for seamless, persistentdebugging tools in any containeror image →Learn more at [URL_WITH_CREDENTIALS] (JY-20676-delete-report-related-obhotos-3-001\(2\)/talon-f.png/Users/lukas/Downloads/Photos-3-001(2)/talon-f.HEIC/Users/lukas/Downloads/Photos-3-001(2)/talon-f.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-ob:hotos-3-001\(2\)/talon-b.png/Users/lukas/Downloads/Photos-3-001(2)/talon-b.HEIC/Users/lukas/Downloads/Photos-3-001(2)/talon-b.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app(JY-20676-delete-report-related-obs-3-001\(2\)/vin.png/Users/lukas/Downloads/Photos-3-001(2)/vin.HEIC/Users/lukas/Downloads/Photos-3-001(2)/vin.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obPhotos-3-001\(2\)/dr-lic-f.png/Users/lukas/Downloads/Photos-3-001(2)/dr-lic-f.HEIC/Users/lukas/Downloads/Photos-3-001(2)/dr-lic-f.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/Jiminny/app (JY-20676-delete-report-related-ob]Photos-3-001\(2\)/dr-lic-b.png/Users/lukas/Downloads/Photos-3-001(2)/dr-lic-b.HEIC/Users/lukas/Downloads/Photos-3-001(2)/dr-lic-b.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obis-3-001\(2)/gtp.pngWarning: /Users/lukas/Downloads/Photos-3-001(2)/gtp.HEIC not a valid file - skippinglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obs-3-001\(2\)/gtp.png/Users/lukas/Downloads/Photos-3-001(2)/gtp.heif/Users/lukas/Downloads/Photos-3-001(2)/gtp.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-ob:/Photos-3-001\(2\)/talon-m-f.png/Users/lukas/Downloads/Photos-3-001(2)/talon-m-f.HEIC/Users/lukas/Downloads/Photos-3-001(2)/talon-m-f.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/Jiminny/appJY-20676-delete-report-related-ob]/Photos-3-001\(2\)/talon-m-b.png/Users/lukas/Downloads/Photos-3-001(2)/talon-m-b.HEIC/Users/lukas/Downloads/Photos-3-001(2)/talon-m-b.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obHomeDMsActivityFilesLater...More+ablAl chapter • 46 m left100% C78•Wed 20 May 17:14:30EDDescribe what you are looking forDirect messages ~UnreadsQ Find a DMNikolay Yankov11 minsаз сьм готов с моята част, местиги в QА като видиш коментаритеAneliya Angelova12:19 PMYou: Ако се наложи ще му пишаStoyan TanevYesterdayYou: Те би трябвало да влизат внова сделка новите activitiesJames Graham• 2018YesterdayStefka StoyanovaYou: в момента ползваме@txt.jiminny.comYesterdayGalya DimitrovaYesterdayедно е ок, няма нужда да гиделим. Той като влезе на едит щ...Vasil Vasilev XблагодаряYesterdayStoyan TomovYou: ако се наложи ще гопреместимMondayTodor Stamatov 0ThursdayNikolay ...MessagesAdd canvasMore ~ушнах за saved search в сьщиябранчToday ~+Saved for later • Due 2 hours agoако искаш виж на планетатаNikolay Yankov 1:01 PMЛукас, има коментари от ClaudeLukas Kovalik 2:35 PMНики при таска за owner rolesтрябва да махнем recorder & voiceгледам че при мен май има самовалидацияте през FE ли са някьдедефинирани?Nikolay Yankov 2:37 PMЗащо, нали го има в условието?Lukas Kovalik 2:38 PMГаля иска да се махнеNikolay Yankov 2:38 PMАха, добре ,ще го махна да я нямав списькаNikolay Yankov 3:25 PMмахнах гоNewNikolay Yankov 5:03 PMаз сьм готов с моята част, местиги в QА като видиш коментаритеMessage Nikolay Yankov+Aa...
|
NULL
|
7511111059804512645
|
NULL
|
click
|
ocr
|
NULL
|
SlackFileEditViewGoHistoryWindowHelpAPPDOCKER₴81DE SlackFileEditViewGoHistoryWindowHelpAPPDOCKER₴81DEV (-zsh)₴2Fixed 0 of 5690 files in 78.144 seconds, 60.00 MBmemory usedWhat's next:Try Docker Debug for seamless, persistentdebugging tools in any containeror image →Learn more at [URL_WITH_CREDENTIALS] (JY-20676-delete-report-related-obhotos-3-001\(2\)/talon-f.png/Users/lukas/Downloads/Photos-3-001(2)/talon-f.HEIC/Users/lukas/Downloads/Photos-3-001(2)/talon-f.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-ob:hotos-3-001\(2\)/talon-b.png/Users/lukas/Downloads/Photos-3-001(2)/talon-b.HEIC/Users/lukas/Downloads/Photos-3-001(2)/talon-b.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app(JY-20676-delete-report-related-obs-3-001\(2\)/vin.png/Users/lukas/Downloads/Photos-3-001(2)/vin.HEIC/Users/lukas/Downloads/Photos-3-001(2)/vin.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obPhotos-3-001\(2\)/dr-lic-f.png/Users/lukas/Downloads/Photos-3-001(2)/dr-lic-f.HEIC/Users/lukas/Downloads/Photos-3-001(2)/dr-lic-f.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/Jiminny/app (JY-20676-delete-report-related-ob]Photos-3-001\(2\)/dr-lic-b.png/Users/lukas/Downloads/Photos-3-001(2)/dr-lic-b.HEIC/Users/lukas/Downloads/Photos-3-001(2)/dr-lic-b.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obis-3-001\(2)/gtp.pngWarning: /Users/lukas/Downloads/Photos-3-001(2)/gtp.HEIC not a valid file - skippinglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obs-3-001\(2\)/gtp.png/Users/lukas/Downloads/Photos-3-001(2)/gtp.heif/Users/lukas/Downloads/Photos-3-001(2)/gtp.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-ob:/Photos-3-001\(2\)/talon-m-f.png/Users/lukas/Downloads/Photos-3-001(2)/talon-m-f.HEIC/Users/lukas/Downloads/Photos-3-001(2)/talon-m-f.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/Jiminny/appJY-20676-delete-report-related-ob]/Photos-3-001\(2\)/talon-m-b.png/Users/lukas/Downloads/Photos-3-001(2)/talon-m-b.HEIC/Users/lukas/Downloads/Photos-3-001(2)/talon-m-b.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obHomeDMsActivityFilesLater...More+ablAl chapter • 46 m left100% C78•Wed 20 May 17:14:30EDDescribe what you are looking forDirect messages ~UnreadsQ Find a DMNikolay Yankov11 minsаз сьм готов с моята част, местиги в QА като видиш коментаритеAneliya Angelova12:19 PMYou: Ако се наложи ще му пишаStoyan TanevYesterdayYou: Те би трябвало да влизат внова сделка новите activitiesJames Graham• 2018YesterdayStefka StoyanovaYou: в момента ползваме@txt.jiminny.comYesterdayGalya DimitrovaYesterdayедно е ок, няма нужда да гиделим. Той като влезе на едит щ...Vasil Vasilev XблагодаряYesterdayStoyan TomovYou: ако се наложи ще гопреместимMondayTodor Stamatov 0ThursdayNikolay ...MessagesAdd canvasMore ~ушнах за saved search в сьщиябранчToday ~+Saved for later • Due 2 hours agoако искаш виж на планетатаNikolay Yankov 1:01 PMЛукас, има коментари от ClaudeLukas Kovalik 2:35 PMНики при таска за owner rolesтрябва да махнем recorder & voiceгледам че при мен май има самовалидацияте през FE ли са някьдедефинирани?Nikolay Yankov 2:37 PMЗащо, нали го има в условието?Lukas Kovalik 2:38 PMГаля иска да се махнеNikolay Yankov 2:38 PMАха, добре ,ще го махна да я нямав списькаNikolay Yankov 3:25 PMмахнах гоNewNikolay Yankov 5:03 PMаз сьм готов с моята част, местиги в QА като видиш коментаритеMessage Nikolay Yankov+Aa...
|
64703
|
NULL
|
NULL
|
NULL
|
|
64706
|
2267
|
6
|
2026-05-20T14:14:32.946744+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779286472946_m2.jpg...
|
Firefox
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira — Work...
|
1
|
jiminny.atlassian.net/jira/software/c/projects/JY/ jiminny.atlassian.net/jira/software/c/projects/JY/boards/37...
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Close tab
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.05905826,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.10721409,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.15791224,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.1796875,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.12699468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-5418029782153561499
|
-981939028105531191
|
click
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Close tab
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
64707
|
2266
|
5
|
2026-05-20T14:14:33.586466+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779286473586_m1.jpg...
|
Firefox
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira — Work...
|
1
|
jiminny.atlassian.net/jira/software/c/projects/JY/ jiminny.atlassian.net/jira/software/c/projects/JY/boards/37...
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Close tab
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage.
4. Performance Overhead
4. Performance Overhead
Expensive Garbage Collection:
Calling
gc_collect_cycles();
on
every single chunk
can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.
Synchronous Metric Dispatch:
If the
Datadog::increment
or
Datadog::gauge
methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.
5. Lack of Explicit Error Handling
5. Lack of Explicit Error Handling
No
try/catch
Blocks:
The script executes complex network operations (
loadDocuments
, Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.
Good response
Bad response
Redo
Share & export
Copy
Show more options...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Main menu","depth":13,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New chat","depth":12,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Chat","depth":13,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":13,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"You said I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a concise summary of the changes made in the code selection:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Refactoring and Dependencies","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Refactoring and Dependencies","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Replaced Interface with Implementation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusInterface","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusManager","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in the imports and constructor dependency injection.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Redis Support:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Imported","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Illuminate\\Support\\Facades\\Redis","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to handle a newly introduced memory-logging lock mechanism.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Traits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WorkerIdTrait","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", along with the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"worker","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"field tracking in the final logs.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Loop and Worker Lifecycle Adjustments","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Loop and Worker Lifecycle Adjustments","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Worker Auto-Restart:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Deleted the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property, the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"constant, and the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"shouldIterateAgain()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method. The script no longer tracks execution iterations or triggers a self-managed worker restart.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Simplified Chunk Method Return:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Modified","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to always return","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Priority Handling for Throttled Entities","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Priority Handling for Throttled Entities","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Stale Record Elevation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Updated the logic in","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"rescheduleThrottledEntities()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to check if a throttled entity group is classified as \"stale\". If it is, the entities are rescheduled using","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"QueuePriorityEnum::NORMAL","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"; otherwise, they keep their respective queue priority.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Metrics, Logging, and Environment Handling","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Metrics, Logging, and Environment Handling","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Environment Tracking:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Introduced a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$environment","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property (defaulting to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'local'","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") and a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"setEnvironment()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method to dynamically assign it.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Memory Monitoring via Datadog Gauge:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method that submits peak memory allocation to Datadog as a gauge metric (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"jiminny.reindex-memory-usage","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis Lock for Metrics Throttling:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Implemented a Redis-backed lock (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"%s-process-manager-lock","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") with a 60-second expiration inside","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to prevent over-reporting metrics.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Log Payload Update:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added total processed items (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$updatedDocuments + $deletedDocuments","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") to the final information log.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":23,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said are there any issues or potential bottlenecks","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"are there any issues or potential bottlenecks","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a breakdown of the potential issues to look out for:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"1. Memory Leak Risk (OOM Crashes)","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. Memory Leak Risk (OOM Crashes)","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Restart Logic:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The most significant change is the removal of the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Dependency Leaks:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If any underlying service (like the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ElasticSearchClient","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", ORM, or Datadog helper) retains references to processed entities,","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Hardcoded Limits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The code still includes","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ini_set('memory_limit', '512M');","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"2. Race Condition in Redis Lock","depth":23,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. Race Condition in Redis Lock","depth":24,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Non-Atomic Operations:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method uses three distinct steps to handle the lock:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"get()","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", and","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"expire()","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If multiple workers process the same","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$this->updateTarget","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"concurrently, they can both pass the","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"if (! is_null(Redis::get(...)))","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"check at the exact same millisecond. Both will then execute","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and push duplicate metrics to Datadog.","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Fix:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This should be replaced with a single atomic operation using Redis's","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"NX","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Not eXists) and","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"EX","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Expire) flags, or Laravel’s built-in atomic cache locks:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis::set($gaugeLockName, true, 'EX', 60, 'NX')","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"3. Infinite Loops and Caller Contract","depth":23,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. Infinite Loops and Caller Contract","depth":24,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Return Value Change:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"previously returned whether it should iterate again based on staleness or chunk size. It now always returns","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(unless the selection is totally empty).","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the code calling this method relies on a","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"false","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"return to pause, sleep, or gracefully terminate, changing this to an unconditional","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"could result in an aggressive \"busy loop\" that spikes CPU usage.","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"4. Performance Overhead","depth":23,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4. Performance Overhead","depth":24,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Expensive Garbage Collection:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Calling","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles();","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"on","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"every single chunk","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Synchronous Metric Dispatch:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Datadog::increment","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"or","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Datadog::gauge","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"5. Lack of Explicit Error Handling","depth":23,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5. Lack of Explicit Error Handling","depth":24,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"try/catch","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Blocks:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The script executes complex network operations (","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"loadDocuments","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":22,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":22,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Redo","depth":22,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":21,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":22,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":21,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
-4126034934648740211
|
-1052937881027004452
|
visual_change
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Close tab
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage.
4. Performance Overhead
4. Performance Overhead
Expensive Garbage Collection:
Calling
gc_collect_cycles();
on
every single chunk
can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.
Synchronous Metric Dispatch:
If the
Datadog::increment
or
Datadog::gauge
methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.
5. Lack of Explicit Error Handling
5. Lack of Explicit Error Handling
No
try/catch
Blocks:
The script executes complex network operations (
loadDocuments
, Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.
Good response
Bad response
Redo
Share & export
Copy
Show more options...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
64708
|
2267
|
7
|
2026-05-20T14:14:36.890502+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779286476890_m2.jpg...
|
Firefox
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira — Work...
|
1
|
jiminny.atlassian.net/jira/software/c/projects/JY/ jiminny.atlassian.net/jira/software/c/projects/JY/boards/37...
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Close tab
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.05905826,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.10721409,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.15791224,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.1796875,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.12699468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":4,"bounds":{"left":0.0,"top":0.21548285,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":5,"bounds":{"left":0.013297873,"top":0.22665602,"width":0.10139628,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"bounds":{"left":0.0,"top":0.2482043,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.25937748,"width":0.18068483,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.28092578,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.29209897,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.31364724,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.32482043,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.3463687,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.3575419,"width":0.15824468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.38068634,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"bounds":{"left":0.35854387,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"bounds":{"left":0.37051198,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Main menu","depth":13,"bounds":{"left":0.08228058,"top":0.09736632,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New chat","depth":12,"bounds":{"left":0.09823803,"top":0.10215483,"width":0.028091755,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":15,"bounds":{"left":0.1008976,"top":0.10654429,"width":0.02144282,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Chat","depth":13,"bounds":{"left":0.35854387,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":13,"bounds":{"left":0.37051198,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"bounds":{"left":0.079288565,"top":0.14445332,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"bounds":{"left":0.079288565,"top":0.14684756,"width":0.1200133,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"You said I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a concise summary of the changes made in the code selection:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Refactoring and Dependencies","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Refactoring and Dependencies","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Replaced Interface with Implementation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusInterface","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusManager","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in the imports and constructor dependency injection.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Redis Support:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Imported","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Illuminate\\Support\\Facades\\Redis","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to handle a newly introduced memory-logging lock mechanism.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Traits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WorkerIdTrait","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", along with the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"worker","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"field tracking in the final logs.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Loop and Worker Lifecycle Adjustments","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Loop and Worker Lifecycle Adjustments","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Worker Auto-Restart:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Deleted the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property, the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"constant, and the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"shouldIterateAgain()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method. The script no longer tracks execution iterations or triggers a self-managed worker restart.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Simplified Chunk Method Return:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Modified","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to always return","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Priority Handling for Throttled Entities","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Priority Handling for Throttled Entities","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Stale Record Elevation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Updated the logic in","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"rescheduleThrottledEntities()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to check if a throttled entity group is classified as \"stale\". If it is, the entities are rescheduled using","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"QueuePriorityEnum::NORMAL","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
3899605034886564096
|
-1061875231230707748
|
visual_change
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Close tab
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
64709
|
2267
|
8
|
2026-05-20T14:14:39.853315+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779286479853_m2.jpg...
|
Firefox
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira — Work...
|
1
|
jiminny.atlassian.net/jira/software/c/projects/JY/ jiminny.atlassian.net/jira/software/c/projects/JY/boards/37...
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Close tab
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage.
4. Performance Overhead
4. Performance Overhead
Expensive Garbage Collection:
Calling
gc_collect_cycles();
on...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.05905826,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.10721409,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.15791224,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.1796875,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.12699468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":4,"bounds":{"left":0.0,"top":0.21548285,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":5,"bounds":{"left":0.013297873,"top":0.22665602,"width":0.10139628,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"bounds":{"left":0.0,"top":0.2482043,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.25937748,"width":0.18068483,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.28092578,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.29209897,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.31364724,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.32482043,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.3463687,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.3575419,"width":0.15824468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.38068634,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"bounds":{"left":0.35854387,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"bounds":{"left":0.37051198,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Main menu","depth":13,"bounds":{"left":0.08228058,"top":0.09736632,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New chat","depth":12,"bounds":{"left":0.09823803,"top":0.10215483,"width":0.028091755,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":15,"bounds":{"left":0.1008976,"top":0.10654429,"width":0.02144282,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Chat","depth":13,"bounds":{"left":0.35854387,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":13,"bounds":{"left":0.37051198,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"bounds":{"left":0.079288565,"top":0.14445332,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"bounds":{"left":0.079288565,"top":0.14684756,"width":0.1200133,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"You said I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a concise summary of the changes made in the code selection:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Refactoring and Dependencies","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Refactoring and Dependencies","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Replaced Interface with Implementation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusInterface","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusManager","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in the imports and constructor dependency injection.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Redis Support:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Imported","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Illuminate\\Support\\Facades\\Redis","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to handle a newly introduced memory-logging lock mechanism.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Traits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WorkerIdTrait","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", along with the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"worker","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"field tracking in the final logs.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Loop and Worker Lifecycle Adjustments","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Loop and Worker Lifecycle Adjustments","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Worker Auto-Restart:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Deleted the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property, the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"constant, and the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"shouldIterateAgain()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method. The script no longer tracks execution iterations or triggers a self-managed worker restart.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Simplified Chunk Method Return:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Modified","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to always return","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Priority Handling for Throttled Entities","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Priority Handling for Throttled Entities","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Stale Record Elevation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Updated the logic in","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"rescheduleThrottledEntities()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to check if a throttled entity group is classified as \"stale\". If it is, the entities are rescheduled using","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"QueuePriorityEnum::NORMAL","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"; otherwise, they keep their respective queue priority.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Metrics, Logging, and Environment Handling","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Metrics, Logging, and Environment Handling","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Environment Tracking:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Introduced a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$environment","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property (defaulting to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'local'","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") and a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"setEnvironment()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method to dynamically assign it.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Memory Monitoring via Datadog Gauge:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method that submits peak memory allocation to Datadog as a gauge metric (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"jiminny.reindex-memory-usage","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis Lock for Metrics Throttling:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Implemented a Redis-backed lock (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"%s-process-manager-lock","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") with a 60-second expiration inside","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to prevent over-reporting metrics.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Log Payload Update:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added total processed items (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$updatedDocuments + $deletedDocuments","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") to the final information log.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":23,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said are there any issues or potential bottlenecks","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"are there any issues or potential bottlenecks","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a breakdown of the potential issues to look out for:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"1. Memory Leak Risk (OOM Crashes)","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. Memory Leak Risk (OOM Crashes)","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Restart Logic:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The most significant change is the removal of the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Dependency Leaks:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If any underlying service (like the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ElasticSearchClient","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", ORM, or Datadog helper) retains references to processed entities,","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"bounds":{"left":0.26811835,"top":0.0,"width":0.056848403,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23138298,"height":0.05546688},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Hardcoded Limits:","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.047041222,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The code still includes","depth":26,"bounds":{"left":0.17154256,"top":0.0,"width":0.05651596,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ini_set('memory_limit', '512M');","depth":27,"bounds":{"left":0.23005319,"top":0.0,"width":0.095744684,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23055187,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"2. Race Condition in Redis Lock","depth":23,"bounds":{"left":0.111369684,"top":0.06823623,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. Race Condition in Redis Lock","depth":24,"bounds":{"left":0.111369684,"top":0.06783719,"width":0.09125665,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Non-Atomic Operations:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.062333778,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The","depth":26,"bounds":{"left":0.18683511,"top":0.09497207,"width":0.011635638,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage","depth":27,"bounds":{"left":0.20046543,"top":0.096568234,"width":0.041888297,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method uses three distinct steps to handle the lock:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.22722739,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"get()","depth":27,"bounds":{"left":0.14860372,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":26,"bounds":{"left":0.16555852,"top":0.11412609,"width":0.002493351,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.17004654,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", and","depth":26,"bounds":{"left":0.18700133,"top":0.11412609,"width":0.013131649,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"expire()","depth":27,"bounds":{"left":0.20212767,"top":0.11572227,"width":0.023936171,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.22805852,"top":0.11412609,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If multiple workers process the same","depth":26,"bounds":{"left":0.16456117,"top":0.14285715,"width":0.09275266,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$this->updateTarget","depth":27,"bounds":{"left":0.25930852,"top":0.14445332,"width":0.056848403,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"concurrently, they can both pass the","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"if (! is_null(Redis::get(...)))","depth":27,"bounds":{"left":0.18351063,"top":0.16360734,"width":0.09275266,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"check at the exact same millisecond. Both will then execute","depth":26,"bounds":{"left":0.12450133,"top":0.16201118,"width":0.21575798,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.2122673,"top":0.18276137,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and push duplicate metrics to Datadog.","depth":26,"bounds":{"left":0.22922207,"top":0.1811652,"width":0.09823803,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Fix:","depth":26,"bounds":{"left":0.12450133,"top":0.20989625,"width":0.019281914,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This should be replaced with a single atomic operation using Redis's","depth":26,"bounds":{"left":0.14378324,"top":0.20989625,"width":0.16821809,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"NX","depth":27,"bounds":{"left":0.31399602,"top":0.21149242,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Not eXists) and","depth":26,"bounds":{"left":0.12450133,"top":0.20989625,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"EX","depth":27,"bounds":{"left":0.13696809,"top":0.23064645,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Expire) flags, or Laravel’s built-in atomic cache locks:","depth":26,"bounds":{"left":0.14494681,"top":0.22905028,"width":0.13264628,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis::set($gaugeLockName, true, 'EX', 60, 'NX')","depth":27,"bounds":{"left":0.12649602,"top":0.24980047,"width":0.14361702,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.27210772,"top":0.2482043,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"3. Infinite Loops and Caller Contract","depth":23,"bounds":{"left":0.111369684,"top":0.2980846,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. Infinite Loops and Caller Contract","depth":24,"bounds":{"left":0.111369684,"top":0.29768556,"width":0.10571808,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Return Value Change:","depth":26,"bounds":{"left":0.12450133,"top":0.32482043,"width":0.055352394,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"bounds":{"left":0.18301196,"top":0.3264166,"width":0.06881649,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"previously returned whether it should iterate again based on staleness or chunk size. It now always returns","depth":26,"bounds":{"left":0.12450133,"top":0.32482043,"width":0.2237367,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"bounds":{"left":0.29438165,"top":0.34557062,"width":0.011968086,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(unless the selection is totally empty).","depth":26,"bounds":{"left":0.12450133,"top":0.34397447,"width":0.2130984,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.39185953,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the code calling this method relies on a","depth":26,"bounds":{"left":0.16456117,"top":0.39185953,"width":0.10305851,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"false","depth":27,"bounds":{"left":0.26961437,"top":0.3934557,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"return to pause, sleep, or gracefully terminate, changing this to an unconditional","depth":26,"bounds":{"left":0.12450133,"top":0.39185953,"width":0.22589761,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"bounds":{"left":0.26163563,"top":0.41260973,"width":0.011968086,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"could result in an aggressive \"busy loop\" that spikes CPU usage.","depth":26,"bounds":{"left":0.12450133,"top":0.41101357,"width":0.22290559,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"4. Performance Overhead","depth":23,"bounds":{"left":0.111369684,"top":0.48004788,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4. Performance Overhead","depth":24,"bounds":{"left":0.111369684,"top":0.47964883,"width":0.076130316,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Expensive Garbage Collection:","depth":26,"bounds":{"left":0.12450133,"top":0.5067837,"width":0.078457445,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Calling","depth":26,"bounds":{"left":0.20295878,"top":0.5067837,"width":0.019281914,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles();","depth":27,"bounds":{"left":0.22423537,"top":0.5083799,"width":0.059840426,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"on","depth":26,"bounds":{"left":0.28607047,"top":0.5067837,"width":0.00880984,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-9199961851185172050
|
-1079960028546893860
|
visual_change
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Close tab
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage.
4. Performance Overhead
4. Performance Overhead
Expensive Garbage Collection:
Calling
gc_collect_cycles();
on...
|
64708
|
NULL
|
NULL
|
NULL
|
|
64710
|
2267
|
9
|
2026-05-20T14:14:41.161160+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779286481161_m2.jpg...
|
Firefox
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira — Work...
|
1
|
jiminny.atlassian.net/jira/software/c/projects/JY/ jiminny.atlassian.net/jira/software/c/projects/JY/boards/37...
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Close tab
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.05905826,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.10721409,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.15791224,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.1796875,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.12699468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":4,"bounds":{"left":0.0,"top":0.21548285,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":5,"bounds":{"left":0.013297873,"top":0.22665602,"width":0.10139628,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"bounds":{"left":0.0,"top":0.2482043,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.25937748,"width":0.18068483,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.28092578,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.29209897,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.31364724,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.32482043,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.3463687,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.3575419,"width":0.15824468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.38068634,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"bounds":{"left":0.35854387,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"bounds":{"left":0.37051198,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Main menu","depth":13,"bounds":{"left":0.08228058,"top":0.09736632,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New chat","depth":12,"bounds":{"left":0.09823803,"top":0.10215483,"width":0.028091755,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":15,"bounds":{"left":0.1008976,"top":0.10654429,"width":0.02144282,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Chat","depth":13,"bounds":{"left":0.35854387,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":13,"bounds":{"left":0.37051198,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"bounds":{"left":0.079288565,"top":0.14445332,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"bounds":{"left":0.079288565,"top":0.14684756,"width":0.1200133,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"You said I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a concise summary of the changes made in the code selection:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Refactoring and Dependencies","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Refactoring and Dependencies","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Replaced Interface with Implementation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusInterface","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusManager","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in the imports and constructor dependency injection.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Redis Support:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Imported","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Illuminate\\Support\\Facades\\Redis","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to handle a newly introduced memory-logging lock mechanism.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Traits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WorkerIdTrait","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", along with the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"worker","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"field tracking in the final logs.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Loop and Worker Lifecycle Adjustments","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Loop and Worker Lifecycle Adjustments","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Worker Auto-Restart:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Deleted the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property, the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"constant, and the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"shouldIterateAgain()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method. The script no longer tracks execution iterations or triggers a self-managed worker restart.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Simplified Chunk Method Return:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Modified","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to always return","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Priority Handling for Throttled Entities","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Priority Handling for Throttled Entities","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Stale Record Elevation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Updated the logic in","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"rescheduleThrottledEntities()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to check if a throttled entity group is classified as \"stale\". If it is, the entities are rescheduled using","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"QueuePriorityEnum::NORMAL","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"; otherwise, they keep their respective queue priority.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Metrics, Logging, and Environment Handling","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Metrics, Logging, and Environment Handling","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Environment Tracking:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Introduced a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$environment","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property (defaulting to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'local'","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") and a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"setEnvironment()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method to dynamically assign it.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Memory Monitoring via Datadog Gauge:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method that submits peak memory allocation to Datadog as a gauge metric (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"jiminny.reindex-memory-usage","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis Lock for Metrics Throttling:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Implemented a Redis-backed lock (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"%s-process-manager-lock","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") with a 60-second expiration inside","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to prevent over-reporting metrics.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Log Payload Update:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added total processed items (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$updatedDocuments + $deletedDocuments","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") to the final information log.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":23,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said are there any issues or potential bottlenecks","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"are there any issues or potential bottlenecks","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
6480823455465750553
|
-881731280493578340
|
click
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Close tab
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
64711
|
2267
|
10
|
2026-05-20T14:14:42.870194+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779286482870_m2.jpg...
|
Firefox
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira — Work...
|
1
|
jiminny.atlassian.net/jira/software/c/projects/JY/ jiminny.atlassian.net/jira/software/c/projects/JY/boards/37...
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Close tab
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.05905826,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.10721409,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.15791224,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.1796875,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.12699468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":4,"bounds":{"left":0.0,"top":0.21548285,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":5,"bounds":{"left":0.013297873,"top":0.22665602,"width":0.10139628,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"bounds":{"left":0.0,"top":0.2482043,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.25937748,"width":0.18068483,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.28092578,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.29209897,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.31364724,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.32482043,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.3463687,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.3575419,"width":0.15824468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.38068634,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"bounds":{"left":0.35854387,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"bounds":{"left":0.37051198,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Main menu","depth":13,"bounds":{"left":0.08228058,"top":0.09736632,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New chat","depth":12,"bounds":{"left":0.09823803,"top":0.10215483,"width":0.028091755,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":15,"bounds":{"left":0.1008976,"top":0.10654429,"width":0.02144282,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Chat","depth":13,"bounds":{"left":0.35854387,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":13,"bounds":{"left":0.37051198,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"bounds":{"left":0.079288565,"top":0.14445332,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"bounds":{"left":0.079288565,"top":0.14684756,"width":0.1200133,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"You said I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a concise summary of the changes made in the code selection:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Refactoring and Dependencies","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Refactoring and Dependencies","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Replaced Interface with Implementation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusInterface","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusManager","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in the imports and constructor dependency injection.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Redis Support:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Imported","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Illuminate\\Support\\Facades\\Redis","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to handle a newly introduced memory-logging lock mechanism.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Traits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WorkerIdTrait","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", along with the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"worker","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"field tracking in the final logs.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Loop and Worker Lifecycle Adjustments","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Loop and Worker Lifecycle Adjustments","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Worker Auto-Restart:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Deleted the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property, the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"constant, and the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"shouldIterateAgain()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method. The script no longer tracks execution iterations or triggers a self-managed worker restart.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Simplified Chunk Method Return:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Modified","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to always return","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Priority Handling for Throttled Entities","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Priority Handling for Throttled Entities","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Stale Record Elevation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Updated the logic in","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"rescheduleThrottledEntities()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to check if a throttled entity group is classified as \"stale\". If it is, the entities are rescheduled using","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"QueuePriorityEnum::NORMAL","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"; otherwise, they keep their respective queue priority.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Metrics, Logging, and Environment Handling","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Metrics, Logging, and Environment Handling","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Environment Tracking:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Introduced a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$environment","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property (defaulting to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'local'","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") and a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"setEnvironment()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method to dynamically assign it.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Memory Monitoring via Datadog Gauge:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method that submits peak memory allocation to Datadog as a gauge metric (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"jiminny.reindex-memory-usage","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis Lock for Metrics Throttling:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Implemented a Redis-backed lock (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"%s-process-manager-lock","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") with a 60-second expiration inside","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to prevent over-reporting metrics.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Log Payload Update:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added total processed items (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$updatedDocuments + $deletedDocuments","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") to the final information log.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":23,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said are there any issues or potential bottlenecks","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"are there any issues or potential bottlenecks","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a breakdown of the potential issues to look out for:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"1. Memory Leak Risk (OOM Crashes)","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. Memory Leak Risk (OOM Crashes)","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Restart Logic:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The most significant change is the removal of the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Dependency Leaks:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If any underlying service (like the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ElasticSearchClient","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", ORM, or Datadog helper) retains references to processed entities,","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"bounds":{"left":0.26811835,"top":0.0,"width":0.056848403,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23138298,"height":0.05546688},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Hardcoded Limits:","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.047041222,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The code still includes","depth":26,"bounds":{"left":0.17154256,"top":0.0,"width":0.05651596,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ini_set('memory_limit', '512M');","depth":27,"bounds":{"left":0.23005319,"top":0.0,"width":0.095744684,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23055187,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"2. Race Condition in Redis Lock","depth":23,"bounds":{"left":0.111369684,"top":0.06823623,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. Race Condition in Redis Lock","depth":24,"bounds":{"left":0.111369684,"top":0.06783719,"width":0.09125665,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Non-Atomic Operations:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.062333778,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The","depth":26,"bounds":{"left":0.18683511,"top":0.09497207,"width":0.011635638,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage","depth":27,"bounds":{"left":0.20046543,"top":0.096568234,"width":0.041888297,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method uses three distinct steps to handle the lock:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.22722739,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"get()","depth":27,"bounds":{"left":0.14860372,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":26,"bounds":{"left":0.16555852,"top":0.11412609,"width":0.002493351,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.17004654,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", and","depth":26,"bounds":{"left":0.18700133,"top":0.11412609,"width":0.013131649,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"expire()","depth":27,"bounds":{"left":0.20212767,"top":0.11572227,"width":0.023936171,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.22805852,"top":0.11412609,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If multiple workers process the same","depth":26,"bounds":{"left":0.16456117,"top":0.14285715,"width":0.09275266,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$this->updateTarget","depth":27,"bounds":{"left":0.25930852,"top":0.14445332,"width":0.056848403,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"concurrently, they can both pass the","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"if (! is_null(Redis::get(...)))","depth":27,"bounds":{"left":0.18351063,"top":0.16360734,"width":0.09275266,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"check at the exact same millisecond. Both will then execute","depth":26,"bounds":{"left":0.12450133,"top":0.16201118,"width":0.21575798,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.2122673,"top":0.18276137,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and push duplicate metrics to Datadog.","depth":26,"bounds":{"left":0.22922207,"top":0.1811652,"width":0.09823803,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Fix:","depth":26,"bounds":{"left":0.12450133,"top":0.20989625,"width":0.019281914,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This should be replaced with a single atomic operation using Redis's","depth":26,"bounds":{"left":0.14378324,"top":0.20989625,"width":0.16821809,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"NX","depth":27,"bounds":{"left":0.31399602,"top":0.21149242,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Not eXists) and","depth":26,"bounds":{"left":0.12450133,"top":0.20989625,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"EX","depth":27,"bounds":{"left":0.13696809,"top":0.23064645,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Expire) flags, or Laravel’s built-in atomic cache locks:","depth":26,"bounds":{"left":0.14494681,"top":0.22905028,"width":0.13264628,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis::set($gaugeLockName, true, 'EX', 60, 'NX')","depth":27,"bounds":{"left":0.12649602,"top":0.24980047,"width":0.14361702,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.27210772,"top":0.2482043,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"3. Infinite Loops and Caller Contract","depth":23,"bounds":{"left":0.111369684,"top":0.2980846,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. Infinite Loops and Caller Contract","depth":24,"bounds":{"left":0.111369684,"top":0.29768556,"width":0.10571808,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
652830244927763980
|
-1061874711537551396
|
visual_change
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Close tab
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract...
|
64710
|
NULL
|
NULL
|
NULL
|
|
64712
|
2267
|
11
|
2026-05-20T14:14:45.873708+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779286485873_m2.jpg...
|
Firefox
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira — Work...
|
1
|
jiminny.atlassian.net/jira/software/c/projects/JY/ jiminny.atlassian.net/jira/software/c/projects/JY/boards/37...
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Close tab
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage.
4. Performance Overhead
4. Performance Overhead
Expensive Garbage Collection:
Calling
gc_collect_cycles();
on
every single chunk
can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.
Synchronous Metric Dispatch:
If the
Datadog::increment
or
Datadog::gauge
methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.
5. Lack of Explicit Error Handling
5. Lack of Explicit Error Handling
No
try/catch
Blocks:
The script executes complex network operations (
loadDocuments
, Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.
Good response
Bad response
Redo...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.05905826,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.10721409,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.15791224,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.1796875,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.12699468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":4,"bounds":{"left":0.0,"top":0.21548285,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":5,"bounds":{"left":0.013297873,"top":0.22665602,"width":0.10139628,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"bounds":{"left":0.0,"top":0.2482043,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.25937748,"width":0.18068483,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.28092578,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.29209897,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.31364724,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.32482043,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.3463687,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.3575419,"width":0.15824468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.38068634,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"bounds":{"left":0.35854387,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"bounds":{"left":0.37051198,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Main menu","depth":13,"bounds":{"left":0.08228058,"top":0.09736632,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New chat","depth":12,"bounds":{"left":0.09823803,"top":0.10215483,"width":0.028091755,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":15,"bounds":{"left":0.1008976,"top":0.10654429,"width":0.02144282,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Chat","depth":13,"bounds":{"left":0.35854387,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":13,"bounds":{"left":0.37051198,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"bounds":{"left":0.079288565,"top":0.14445332,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"bounds":{"left":0.079288565,"top":0.14684756,"width":0.1200133,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"You said I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a concise summary of the changes made in the code selection:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Refactoring and Dependencies","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Refactoring and Dependencies","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Replaced Interface with Implementation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusInterface","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusManager","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in the imports and constructor dependency injection.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Redis Support:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Imported","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Illuminate\\Support\\Facades\\Redis","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to handle a newly introduced memory-logging lock mechanism.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Traits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WorkerIdTrait","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", along with the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"worker","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"field tracking in the final logs.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Loop and Worker Lifecycle Adjustments","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Loop and Worker Lifecycle Adjustments","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Worker Auto-Restart:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Deleted the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property, the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"constant, and the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"shouldIterateAgain()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method. The script no longer tracks execution iterations or triggers a self-managed worker restart.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Simplified Chunk Method Return:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Modified","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to always return","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Priority Handling for Throttled Entities","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Priority Handling for Throttled Entities","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Stale Record Elevation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Updated the logic in","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"rescheduleThrottledEntities()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to check if a throttled entity group is classified as \"stale\". If it is, the entities are rescheduled using","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"QueuePriorityEnum::NORMAL","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"; otherwise, they keep their respective queue priority.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Metrics, Logging, and Environment Handling","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Metrics, Logging, and Environment Handling","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Environment Tracking:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Introduced a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$environment","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property (defaulting to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'local'","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") and a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"setEnvironment()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method to dynamically assign it.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Memory Monitoring via Datadog Gauge:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method that submits peak memory allocation to Datadog as a gauge metric (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"jiminny.reindex-memory-usage","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis Lock for Metrics Throttling:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Implemented a Redis-backed lock (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"%s-process-manager-lock","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") with a 60-second expiration inside","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to prevent over-reporting metrics.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Log Payload Update:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added total processed items (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$updatedDocuments + $deletedDocuments","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") to the final information log.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":23,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said are there any issues or potential bottlenecks","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"are there any issues or potential bottlenecks","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a breakdown of the potential issues to look out for:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"1. Memory Leak Risk (OOM Crashes)","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. Memory Leak Risk (OOM Crashes)","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Restart Logic:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The most significant change is the removal of the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Dependency Leaks:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If any underlying service (like the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ElasticSearchClient","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", ORM, or Datadog helper) retains references to processed entities,","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"bounds":{"left":0.26811835,"top":0.0,"width":0.056848403,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23138298,"height":0.05546688},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Hardcoded Limits:","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.047041222,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The code still includes","depth":26,"bounds":{"left":0.17154256,"top":0.0,"width":0.05651596,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ini_set('memory_limit', '512M');","depth":27,"bounds":{"left":0.23005319,"top":0.0,"width":0.095744684,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23055187,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"2. Race Condition in Redis Lock","depth":23,"bounds":{"left":0.111369684,"top":0.06823623,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. Race Condition in Redis Lock","depth":24,"bounds":{"left":0.111369684,"top":0.06783719,"width":0.09125665,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Non-Atomic Operations:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.062333778,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The","depth":26,"bounds":{"left":0.18683511,"top":0.09497207,"width":0.011635638,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage","depth":27,"bounds":{"left":0.20046543,"top":0.096568234,"width":0.041888297,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method uses three distinct steps to handle the lock:","depth":26,"bounds":{"left":0.12450133,"top":0.09497207,"width":0.22722739,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"get()","depth":27,"bounds":{"left":0.14860372,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":26,"bounds":{"left":0.16555852,"top":0.11412609,"width":0.002493351,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.17004654,"top":0.11572227,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", and","depth":26,"bounds":{"left":0.18700133,"top":0.11412609,"width":0.013131649,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"expire()","depth":27,"bounds":{"left":0.20212767,"top":0.11572227,"width":0.023936171,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.22805852,"top":0.11412609,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If multiple workers process the same","depth":26,"bounds":{"left":0.16456117,"top":0.14285715,"width":0.09275266,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$this->updateTarget","depth":27,"bounds":{"left":0.25930852,"top":0.14445332,"width":0.056848403,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"concurrently, they can both pass the","depth":26,"bounds":{"left":0.12450133,"top":0.14285715,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"if (! is_null(Redis::get(...)))","depth":27,"bounds":{"left":0.18351063,"top":0.16360734,"width":0.09275266,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"check at the exact same millisecond. Both will then execute","depth":26,"bounds":{"left":0.12450133,"top":0.16201118,"width":0.21575798,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.2122673,"top":0.18276137,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and push duplicate metrics to Datadog.","depth":26,"bounds":{"left":0.22922207,"top":0.1811652,"width":0.09823803,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Fix:","depth":26,"bounds":{"left":0.12450133,"top":0.20989625,"width":0.019281914,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This should be replaced with a single atomic operation using Redis's","depth":26,"bounds":{"left":0.14378324,"top":0.20989625,"width":0.16821809,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"NX","depth":27,"bounds":{"left":0.31399602,"top":0.21149242,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Not eXists) and","depth":26,"bounds":{"left":0.12450133,"top":0.20989625,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"EX","depth":27,"bounds":{"left":0.13696809,"top":0.23064645,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Expire) flags, or Laravel’s built-in atomic cache locks:","depth":26,"bounds":{"left":0.14494681,"top":0.22905028,"width":0.13264628,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis::set($gaugeLockName, true, 'EX', 60, 'NX')","depth":27,"bounds":{"left":0.12649602,"top":0.24980047,"width":0.14361702,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.27210772,"top":0.2482043,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"3. Infinite Loops and Caller Contract","depth":23,"bounds":{"left":0.111369684,"top":0.2980846,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. Infinite Loops and Caller Contract","depth":24,"bounds":{"left":0.111369684,"top":0.29768556,"width":0.10571808,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Return Value Change:","depth":26,"bounds":{"left":0.12450133,"top":0.32482043,"width":0.055352394,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"bounds":{"left":0.18301196,"top":0.3264166,"width":0.06881649,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"previously returned whether it should iterate again based on staleness or chunk size. It now always returns","depth":26,"bounds":{"left":0.12450133,"top":0.32482043,"width":0.2237367,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"bounds":{"left":0.29438165,"top":0.34557062,"width":0.011968086,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(unless the selection is totally empty).","depth":26,"bounds":{"left":0.12450133,"top":0.34397447,"width":0.2130984,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.39185953,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the code calling this method relies on a","depth":26,"bounds":{"left":0.16456117,"top":0.39185953,"width":0.10305851,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"false","depth":27,"bounds":{"left":0.26961437,"top":0.3934557,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"return to pause, sleep, or gracefully terminate, changing this to an unconditional","depth":26,"bounds":{"left":0.12450133,"top":0.39185953,"width":0.22589761,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"bounds":{"left":0.26163563,"top":0.41260973,"width":0.011968086,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"could result in an aggressive \"busy loop\" that spikes CPU usage.","depth":26,"bounds":{"left":0.12450133,"top":0.41101357,"width":0.22290559,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"4. Performance Overhead","depth":23,"bounds":{"left":0.111369684,"top":0.48004788,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4. Performance Overhead","depth":24,"bounds":{"left":0.111369684,"top":0.47964883,"width":0.076130316,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Expensive Garbage Collection:","depth":26,"bounds":{"left":0.12450133,"top":0.5067837,"width":0.078457445,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Calling","depth":26,"bounds":{"left":0.20295878,"top":0.5067837,"width":0.019281914,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles();","depth":27,"bounds":{"left":0.22423537,"top":0.5083799,"width":0.059840426,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"on","depth":26,"bounds":{"left":0.28607047,"top":0.5067837,"width":0.00880984,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"every single chunk","depth":26,"bounds":{"left":0.29488033,"top":0.5067837,"width":0.045545213,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.","depth":26,"bounds":{"left":0.12450133,"top":0.5067837,"width":0.23420878,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Synchronous Metric Dispatch:","depth":26,"bounds":{"left":0.12450133,"top":0.5738228,"width":0.07712766,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the","depth":26,"bounds":{"left":0.20162898,"top":0.5738228,"width":0.015292553,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Datadog::increment","depth":27,"bounds":{"left":0.21891622,"top":0.575419,"width":0.053856384,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"or","depth":26,"bounds":{"left":0.27476728,"top":0.5738228,"width":0.0078125,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Datadog::gauge","depth":27,"bounds":{"left":0.28457448,"top":0.575419,"width":0.041888297,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.","depth":26,"bounds":{"left":0.12450133,"top":0.5738228,"width":0.23420878,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"5. Lack of Explicit Error Handling","depth":23,"bounds":{"left":0.111369684,"top":0.66201115,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5. Lack of Explicit Error Handling","depth":24,"bounds":{"left":0.111369684,"top":0.66161215,"width":0.094913565,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No","depth":26,"bounds":{"left":0.12450133,"top":0.688747,"width":0.008477394,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"try/catch","depth":27,"bounds":{"left":0.1349734,"top":0.6903432,"width":0.026928192,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Blocks:","depth":26,"bounds":{"left":0.16389628,"top":0.688747,"width":0.019614361,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The script executes complex network operations (","depth":26,"bounds":{"left":0.18351063,"top":0.688747,"width":0.12367021,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"loadDocuments","depth":27,"bounds":{"left":0.30917552,"top":0.6903432,"width":0.038896278,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.","depth":26,"bounds":{"left":0.12450133,"top":0.688747,"width":0.22888963,"height":0.09377494},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":22,"bounds":{"left":0.1087101,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":22,"bounds":{"left":0.124667555,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Redo","depth":22,"bounds":{"left":0.140625,"top":0.79768556,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
2770962430260122261
|
-1052937881026988068
|
visual_change
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Close tab
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage.
4. Performance Overhead
4. Performance Overhead
Expensive Garbage Collection:
Calling
gc_collect_cycles();
on
every single chunk
can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.
Synchronous Metric Dispatch:
If the
Datadog::increment
or
Datadog::gauge
methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.
5. Lack of Explicit Error Handling
5. Lack of Explicit Error Handling
No
try/catch
Blocks:
The script executes complex network operations (
loadDocuments
, Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.
Good response
Bad response
Redo...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
64729
|
2266
|
16
|
2026-05-20T14:16:03.792622+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779286563792_m1.jpg...
|
Firefox
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira — Work...
|
1
|
jiminny.atlassian.net/jira/software/c/projects/JY/ jiminny.atlassian.net/jira/software/c/projects/JY/boards/37...
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Close tab
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-8118722874848706322
|
8239178460156486089
|
click
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Close tab
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
64730
|
2267
|
18
|
2026-05-20T14:16:03.791379+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779286563791_m2.jpg...
|
Firefox
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira — Work...
|
1
|
jiminny.atlassian.net/jira/software/c/projects/JY/ jiminny.atlassian.net/jira/software/c/projects/JY/boards/37...
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Close tab
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.05905826,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.10721409,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.15791224,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
-7886941461675742164
|
8239181228799783117
|
click
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Close tab
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
64731
|
2267
|
19
|
2026-05-20T14:16:04.731308+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779286564731_m2.jpg...
|
Firefox
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira — Work...
|
1
|
jiminny.atlassian.net/jira/software/c/projects/JY/ jiminny.atlassian.net/jira/software/c/projects/JY/boards/37...
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Close tab
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage.
4. Performance Overhead
4. Performance Overhead
Expensive Garbage Collection:
Calling
gc_collect_cycles();
on
every single chunk
can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.
Synchronous Metric Dispatch:
If the
Datadog::increment
or
Datadog::gauge
methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.
5. Lack of Explicit Error Handling
5. Lack of Explicit Error Handling
No
try/catch
Blocks:
The script executes complex network operations (
loadDocuments
, Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.
Good response
Bad response
Redo
Share & export
Copy
Show more options...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.05905826,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.10721409,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.15791224,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.1796875,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.12699468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":4,"bounds":{"left":0.0,"top":0.21548285,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Coverage on New Code - app in Jiminny SonarQube Cloud","depth":5,"bounds":{"left":0.013297873,"top":0.22665602,"width":0.10139628,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"bounds":{"left":0.0,"top":0.2482043,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.25937748,"width":0.18068483,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.28092578,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.29209897,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.31364724,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.32482043,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.3463687,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20808 schedule reset on low queue by Vasil-Jiminny · Pull Request #12106 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.3575419,"width":0.15824468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.38068634,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"bounds":{"left":0.35854387,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"bounds":{"left":0.37051198,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Main menu","depth":13,"bounds":{"left":0.08228058,"top":0.09736632,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New chat","depth":12,"bounds":{"left":0.09823803,"top":0.10215483,"width":0.028091755,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":15,"bounds":{"left":0.1008976,"top":0.10654429,"width":0.02144282,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Chat","depth":13,"bounds":{"left":0.35854387,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":13,"bounds":{"left":0.37051198,"top":0.10215483,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"bounds":{"left":0.079288565,"top":0.14445332,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"bounds":{"left":0.079288565,"top":0.14684756,"width":0.1200133,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"You said I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\\LaravelDatadogHelper\\Datadog;7use ChaseConey\\LaravelDatadogHelper\\Datadog;8use Illuminate\\Support\\Facades\\Log;8use Illuminate\\Support\\Facades\\Log;9+use Illuminate\\Support\\Facades\\Redis;9use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\Client as ElasticSearchClient;10use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ElasticSearch\\ElasticSearchMappings;11use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\DeleteDocumentsAction;12use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\LoadDocumentsAction;13use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14use Jiminny\\Component\\ES\\Processor\\Actions\\UpsertDocumentsAction;14-use Jiminny\\Component\\ES\\Processor\\BatchStatusInterface;15+use Jiminny\\Component\\ES\\Processor\\BatchStatusManager;15use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SelectionList;16use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\DTOs\\SimpleCollection;17use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\TargetEntitiesSelector;18use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\ExtractIdsTrait;19use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\SelectEntityListTrait;20use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21use Jiminny\\Component\\ES\\Processor\\Traits\\UpdateTargetTrait;21-use Jiminny\\Component\\ES\\Processor\\Traits\\WorkerIdTrait;22use Jiminny\\Utils\\StringUtil;22use Jiminny\\Utils\\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a concise summary of the changes made in the code selection:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Refactoring and Dependencies","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Refactoring and Dependencies","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Replaced Interface with Implementation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusInterface","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BatchStatusManager","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in the imports and constructor dependency injection.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Redis Support:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Imported","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Illuminate\\Support\\Facades\\Redis","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to handle a newly introduced memory-logging lock mechanism.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Traits:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WorkerIdTrait","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", along with the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"worker","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"field tracking in the final logs.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Loop and Worker Lifecycle Adjustments","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Loop and Worker Lifecycle Adjustments","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Worker Auto-Restart:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Deleted the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property, the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"constant, and the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"shouldIterateAgain()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method. The script no longer tracks execution iterations or triggers a self-managed worker restart.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Simplified Chunk Method Return:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Modified","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to always return","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Priority Handling for Throttled Entities","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Priority Handling for Throttled Entities","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Stale Record Elevation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Updated the logic in","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"rescheduleThrottledEntities()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to check if a throttled entity group is classified as \"stale\". If it is, the entities are rescheduled using","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"QueuePriorityEnum::NORMAL","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"; otherwise, they keep their respective queue priority.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Metrics, Logging, and Environment Handling","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Metrics, Logging, and Environment Handling","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added Environment Tracking:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Introduced a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$environment","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"property (defaulting to","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'local'","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") and a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"setEnvironment()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method to dynamically assign it.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Memory Monitoring via Datadog Gauge:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method that submits peak memory allocation to Datadog as a gauge metric (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"jiminny.reindex-memory-usage","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis Lock for Metrics Throttling:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Implemented a Redis-backed lock (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"%s-process-manager-lock","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") with a 60-second expiration inside","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to prevent over-reporting metrics.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Log Payload Update:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added total processed items (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$updatedDocuments + $deletedDocuments","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") to the final information log.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":23,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said are there any issues or potential bottlenecks","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"are there any issues or potential bottlenecks","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a breakdown of the potential issues to look out for:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"1. Memory Leak Risk (OOM Crashes)","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. Memory Leak Risk (OOM Crashes)","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Removed Restart Logic:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The most significant change is the removal of the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$counter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RESTART_AFTER","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Dependency Leaks:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If any underlying service (like the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ElasticSearchClient","depth":27,"bounds":{"left":0.25930852,"top":0.0,"width":0.056848403,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", ORM, or Datadog helper) retains references to processed entities,","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles()","depth":27,"bounds":{"left":0.26811835,"top":0.0,"width":0.056848403,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23138298,"height":0.05546688},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Hardcoded Limits:","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.047041222,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The code still includes","depth":26,"bounds":{"left":0.17154256,"top":0.0,"width":0.05651596,"height":0.01715882},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ini_set('memory_limit', '512M');","depth":27,"bounds":{"left":0.23005319,"top":0.0,"width":0.095744684,"height":0.015163607},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.","depth":26,"bounds":{"left":0.12450133,"top":0.0,"width":0.23055187,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"2. Race Condition in Redis Lock","depth":23,"bounds":{"left":0.111369684,"top":0.073822826,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. Race Condition in Redis Lock","depth":24,"bounds":{"left":0.111369684,"top":0.07342378,"width":0.09125665,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Non-Atomic Operations:","depth":26,"bounds":{"left":0.12450133,"top":0.10055866,"width":0.062333778,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The","depth":26,"bounds":{"left":0.18683511,"top":0.10055866,"width":0.011635638,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logMemoryUsage","depth":27,"bounds":{"left":0.20046543,"top":0.10215483,"width":0.041888297,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method uses three distinct steps to handle the lock:","depth":26,"bounds":{"left":0.12450133,"top":0.10055866,"width":0.22722739,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"get()","depth":27,"bounds":{"left":0.14860372,"top":0.121308856,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":26,"bounds":{"left":0.16555852,"top":0.11971269,"width":0.002493351,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.17004654,"top":0.121308856,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", and","depth":26,"bounds":{"left":0.18700133,"top":0.11971269,"width":0.013131649,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"expire()","depth":27,"bounds":{"left":0.20212767,"top":0.121308856,"width":0.023936171,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.22805852,"top":0.11971269,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.14844373,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If multiple workers process the same","depth":26,"bounds":{"left":0.16456117,"top":0.14844373,"width":0.09275266,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$this->updateTarget","depth":27,"bounds":{"left":0.25930852,"top":0.15003991,"width":0.056848403,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"concurrently, they can both pass the","depth":26,"bounds":{"left":0.12450133,"top":0.14844373,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"if (! is_null(Redis::get(...)))","depth":27,"bounds":{"left":0.18351063,"top":0.16919394,"width":0.09275266,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"check at the exact same millisecond. Both will then execute","depth":26,"bounds":{"left":0.12450133,"top":0.16759777,"width":0.21575798,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"set()","depth":27,"bounds":{"left":0.2122673,"top":0.18834797,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and push duplicate metrics to Datadog.","depth":26,"bounds":{"left":0.22922207,"top":0.1867518,"width":0.09823803,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Fix:","depth":26,"bounds":{"left":0.12450133,"top":0.21548285,"width":0.019281914,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This should be replaced with a single atomic operation using Redis's","depth":26,"bounds":{"left":0.14378324,"top":0.21548285,"width":0.16821809,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"NX","depth":27,"bounds":{"left":0.31399602,"top":0.21707901,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Not eXists) and","depth":26,"bounds":{"left":0.12450133,"top":0.21548285,"width":0.22855718,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"EX","depth":27,"bounds":{"left":0.13696809,"top":0.23623304,"width":0.005984043,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Expire) flags, or Laravel’s built-in atomic cache locks:","depth":26,"bounds":{"left":0.14494681,"top":0.23463687,"width":0.13264628,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Redis::set($gaugeLockName, true, 'EX', 60, 'NX')","depth":27,"bounds":{"left":0.12649602,"top":0.25538707,"width":0.14361702,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"bounds":{"left":0.27210772,"top":0.25379092,"width":0.0013297872,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"3. Infinite Loops and Caller Contract","depth":23,"bounds":{"left":0.111369684,"top":0.30367118,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. Infinite Loops and Caller Contract","depth":24,"bounds":{"left":0.111369684,"top":0.30327216,"width":0.10571808,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Return Value Change:","depth":26,"bounds":{"left":0.12450133,"top":0.33040702,"width":0.055352394,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"doUpdateEntitiesChunk()","depth":27,"bounds":{"left":0.18301196,"top":0.3320032,"width":0.06881649,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"previously returned whether it should iterate again based on staleness or chunk size. It now always returns","depth":26,"bounds":{"left":0.12450133,"top":0.33040702,"width":0.2237367,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"bounds":{"left":0.29438165,"top":0.35115722,"width":0.011968086,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(unless the selection is totally empty).","depth":26,"bounds":{"left":0.12450133,"top":0.34956107,"width":0.2130984,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Bottleneck:","depth":26,"bounds":{"left":0.12450133,"top":0.39744613,"width":0.040059842,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the code calling this method relies on a","depth":26,"bounds":{"left":0.16456117,"top":0.39744613,"width":0.10305851,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"false","depth":27,"bounds":{"left":0.26961437,"top":0.3990423,"width":0.014960106,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"return to pause, sleep, or gracefully terminate, changing this to an unconditional","depth":26,"bounds":{"left":0.12450133,"top":0.39744613,"width":0.22589761,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":27,"bounds":{"left":0.26163563,"top":0.41819632,"width":0.011968086,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"could result in an aggressive \"busy loop\" that spikes CPU usage.","depth":26,"bounds":{"left":0.12450133,"top":0.41660017,"width":0.22290559,"height":0.03631285},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"4. Performance Overhead","depth":23,"bounds":{"left":0.111369684,"top":0.48563448,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4. Performance Overhead","depth":24,"bounds":{"left":0.111369684,"top":0.48523542,"width":0.076130316,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Expensive Garbage Collection:","depth":26,"bounds":{"left":0.12450133,"top":0.5123703,"width":0.078457445,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Calling","depth":26,"bounds":{"left":0.20295878,"top":0.5123703,"width":0.019281914,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"gc_collect_cycles();","depth":27,"bounds":{"left":0.22423537,"top":0.5139665,"width":0.059840426,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"on","depth":26,"bounds":{"left":0.28607047,"top":0.5123703,"width":0.00880984,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"every single chunk","depth":26,"bounds":{"left":0.29488033,"top":0.5123703,"width":0.045545213,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.","depth":26,"bounds":{"left":0.12450133,"top":0.5123703,"width":0.23420878,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Synchronous Metric Dispatch:","depth":26,"bounds":{"left":0.12450133,"top":0.5794094,"width":0.07712766,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the","depth":26,"bounds":{"left":0.20162898,"top":0.5794094,"width":0.015292553,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Datadog::increment","depth":27,"bounds":{"left":0.21891622,"top":0.5810056,"width":0.053856384,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"or","depth":26,"bounds":{"left":0.27476728,"top":0.5794094,"width":0.0078125,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Datadog::gauge","depth":27,"bounds":{"left":0.28457448,"top":0.5810056,"width":0.041888297,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.","depth":26,"bounds":{"left":0.12450133,"top":0.5794094,"width":0.23420878,"height":0.05546688},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"5. Lack of Explicit Error Handling","depth":23,"bounds":{"left":0.111369684,"top":0.6675978,"width":0.24734043,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5. Lack of Explicit Error Handling","depth":24,"bounds":{"left":0.111369684,"top":0.6671987,"width":0.094913565,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No","depth":26,"bounds":{"left":0.12450133,"top":0.6943336,"width":0.008477394,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"try/catch","depth":27,"bounds":{"left":0.1349734,"top":0.69592977,"width":0.026928192,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Blocks:","depth":26,"bounds":{"left":0.16389628,"top":0.6943336,"width":0.019614361,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The script executes complex network operations (","depth":26,"bounds":{"left":0.18351063,"top":0.6943336,"width":0.12367021,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"loadDocuments","depth":27,"bounds":{"left":0.30917552,"top":0.69592977,"width":0.038896278,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.","depth":26,"bounds":{"left":0.12450133,"top":0.6943336,"width":0.22888963,"height":0.09377494},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":22,"bounds":{"left":0.1087101,"top":0.8032721,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":22,"bounds":{"left":0.124667555,"top":0.8032721,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Redo","depth":22,"bounds":{"left":0.140625,"top":0.8032721,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":21,"bounds":{"left":0.15658244,"top":0.8032721,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":22,"bounds":{"left":0.17253989,"top":0.8032721,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":21,"bounds":{"left":0.18849733,"top":0.8032721,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
-4126034934648740211
|
-1052937881027004452
|
visual_change
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Close tab
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
Allow owner's role to be selected when setting up a trial by LakyLak · Pull Request #12092 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
JY-20915 add alias for EU by LakyLak · Pull Request #12105 · jiminny/app
Coverage on New Code - app in Jiminny SonarQube Cloud
Coverage on New Code - app in Jiminny SonarQube Cloud
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20808 schedule reset on low queue by Vasil-Jimi</tabTitle>” with “<selection>@@ -6,38 +6,35 @@667use ChaseConey\LaravelDatadogHelper\Datadog;7use ChaseConey\LaravelDatadogHelper\Datadog;8use Illuminate\Support\Facades\Log;8use Illuminate\Support\Facades\Log;9+use Illuminate\Support\Facades\Redis;9use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\Client as ElasticSearchClient;10use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ElasticSearch\ElasticSearchMappings;11use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\DeleteDocumentsAction;12use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\LoadDocumentsAction;13use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14use Jiminny\Component\ES\Processor\Actions\UpsertDocumentsAction;14-use Jiminny\Component\ES\Processor\BatchStatusInterface;15+use Jiminny\Component\ES\Processor\BatchStatusManager;15use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SelectionList;16use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\DTOs\SimpleCollection;17use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\TargetEntitiesSelector;18use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\ExtractIdsTrait;19use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\SelectEntityListTrait;20use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21use Jiminny\Component\ES\Processor\Traits\UpdateTargetTrait;21-use Jiminny\Component\ES\Processor\Traits\WorkerIdTrait;22use Jiminny\Utils\StringUtil;22use Jiminny\Utils\StringUtil;232324class UpdateProcessManager24class UpdateProcessManager25{25{26use ExtractIdsTrait;26use ExtractIdsTrait;27use SelectEntityListTrait;27use SelectEntityListTrait;28use UpdateTargetTrait;28use UpdateTargetTrait;29-use WorkerIdTrait;302931-/**30+private const string GAUGE_LOCK_KEY = '%s-process-manager-lock';32- * Restart the script every X iterations, to allow the OS to collect any unused memory31+33- */32+private string $environment = 'local';34-private const int RESTART_AFTER = 20; // Iterations35-private int $counter = 0;363337public function __construct(34public function __construct(38private readonly ElasticSearchClient $esClient,35private readonly ElasticSearchClient $esClient,39private readonly TargetEntitiesSelector $entitySelector,36private readonly TargetEntitiesSelector $entitySelector,40-private readonly BatchStatusInterface $batchStatusManager,37+private readonly BatchStatusManager $batchStatusManager,41private readonly DeleteDocumentsAction $deleteDocumentsAction,38private readonly DeleteDocumentsAction $deleteDocumentsAction,42private readonly UpsertDocumentsAction $upsertDocumentsAction,39private readonly UpsertDocumentsAction $upsertDocumentsAction,43private readonly LoadDocumentsAction $loadDocumentsAction,40private readonly LoadDocumentsAction $loadDocumentsAction,@@ -49,6 +46,11 @@ public function __construct(49ini_set('memory_limit', '512M');46ini_set('memory_limit', '512M');50 }47 }514849+public function setEnvironment(string $environment): void50+ {51+$this->environment = $environment;52+ }53+52public function bootstrap(): void54public function bootstrap(): void53 {55 {54// Prepare the batch status manager56// Prepare the batch status manager@@ -73,21 +75,15 @@ public function bootstrap(): void737574public function doUpdateEntitiesChunk(): bool76public function doUpdateEntitiesChunk(): bool75 {77 {76-$batchName = $this->batchStatusManager->generateName();77$startTime = microtime(true);78$startTime = microtime(true);79+$batchName = $this->batchStatusManager->generateName();788079/** Select Entity IDs for processing */81/** Select Entity IDs for processing */80$selectionList = $this->entitySelector->select();82$selectionList = $this->entitySelector->select();81if ($selectionList->isEmpty()) {83if ($selectionList->isEmpty()) {82return false;84return false;83 }85 }848685-/**86- * A stale chunk will always be smaller than a regular chunk.87- * That doesn't mean there aren't any other scheduled entities for processing88- */89-$shouldIterateAgain = $this->shouldIterateAgain($selectionList);90-91/** Mark the batch as scheduled for work, then load the actual documents */87/** Mark the batch as scheduled for work, then load the actual documents */92$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);88$this->batchStatusManager->scheduleWork($selectionList->getAllIds(), $batchName);93$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());89$documentLoad = $this->loadDocumentsAction->loadDocuments($this->updateTarget, $selectionList->getAllIds());@@ -103,42 +99,23 @@ public function doUpdateEntitiesChunk(): bool103 unset($selectionList);99 unset($selectionList);104gc_collect_cycles();100gc_collect_cycles();105101106-/**102+/** Write stats and logs */107- * Write stats and logs108- */109 Datadog::increment(103 Datadog::increment(110 stats: 'jiminny.es-documents-synced',104 stats: 'jiminny.es-documents-synced',111 sampleRate: 1,105 sampleRate: 1,112 value: $updatedDocuments + $deletedDocuments106 value: $updatedDocuments + $deletedDocuments113 );107 );114108109+$this->logMemoryUsage();110+115 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [111 Log::info('[ EsUpdateProcessManager ] Finished updating entities in ES', [116-'worker' => $this->workerId,117'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),112'peak_memory' => StringUtil::filesizeToHuman(memory_get_peak_usage(true)),118'elapsed_seconds' => round(microtime(true) - $startTime, 2),113'elapsed_seconds' => round(microtime(true) - $startTime, 2),119'update_target' => $this->updateTarget,114'update_target' => $this->updateTarget,120-'should_iterate_again' => $shouldIterateAgain,115+'processed' => $updatedDocuments + $deletedDocuments,121 ]);116 ]);122117123-$this->counter++;118+return true;124-125-return $shouldIterateAgain;126- }127-128-private function shouldIterateAgain(SelectionList $selectionList): bool129- {130-if ($selectionList->isStaleChunkSelected() || $selectionList->isFullChunk()) {131-/**132- * Restart the worker to allow the OS to collect any unused memory133- */134-if ($this->counter >= self::RESTART_AFTER) {135-return false;136- }137-138-return true;139- }140-141-return false;142 }119 }143120144private function executeDelete(SimpleCollection $documentsToDelete): int121private function executeDelete(SimpleCollection $documentsToDelete): int@@ -162,13 +139,41 @@ private function rescheduleThrottledEntities(SelectionList $selectionList): void162$groups = $selectionList->getEntityListGroups();139$groups = $selectionList->getEntityListGroups();163foreach ($groups as $eachGroup) {140foreach ($groups as $eachGroup) {164$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);141$groupEntityIds = $selectionList->getEntityListItemsByGroup($eachGroup);142+$groupPriority = QueuePriorityEnum::tryFromString($eachGroup);143+165if (! empty($groupEntityIds)) {144if (! empty($groupEntityIds)) {145+/**146+ * Throttled stale records are rescheduled as normal priority147+ * all other records are rescheduled to their respective queue148+ */166 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(149 AsyncUpdateElasticSearch::bulkMarkEntitiesForUpdate(167 entityType: $this->updateTarget,150 entityType: $this->updateTarget,168 entityIds: $groupEntityIds,151 entityIds: $groupEntityIds,169- priority: QueuePriorityEnum::tryFromString($eachGroup)152+ priority: $groupPriority->isStale() ? QueuePriorityEnum::NORMAL : $groupPriority,170 );153 );171 }154 }172 }155 }173 }156 }157+158+private function logMemoryUsage(): void159+ {160+$gaugeLockName = sprintf(self::GAUGE_LOCK_KEY, $this->updateTarget);161+if (! is_null(Redis::get($gaugeLockName))) {162+return;163+ }164+165+ Redis::set($gaugeLockName, true);166+ Redis::expire($gaugeLockName, 60);167+168+// Add memory usage of the process as well169+ Datadog::gauge(170+ stat: 'jiminny.reindex-memory-usage',171+ value: memory_get_peak_usage(true),172+ sampleRate: 1,173+ tags: [174+'environment' => $this->environment,175+'type' => $this->updateTarget,176+ ]177+ );178+ }174}179}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Copy prompt
Gemini said
Gemini said
Here is a concise summary of the changes made in the code selection:
Refactoring and Dependencies
Refactoring and Dependencies
Replaced Interface with Implementation:
Changed
BatchStatusInterface
to
BatchStatusManager
in the imports and constructor dependency injection.
Added Redis Support:
Imported
Illuminate\Support\Facades\Redis
to handle a newly introduced memory-logging lock mechanism.
Removed Traits:
Removed
WorkerIdTrait
, along with the
worker
field tracking in the final logs.
Loop and Worker Lifecycle Adjustments
Loop and Worker Lifecycle Adjustments
Removed Worker Auto-Restart:
Deleted the
$counter
property, the
RESTART_AFTER
constant, and the
shouldIterateAgain()
method. The script no longer tracks execution iterations or triggers a self-managed worker restart.
Simplified Chunk Method Return:
Modified
doUpdateEntitiesChunk()
to always return
true
upon completion, rather than determining whether to iterate again based on chunk fullness or staleness.
Priority Handling for Throttled Entities
Priority Handling for Throttled Entities
Stale Record Elevation:
Updated the logic in
rescheduleThrottledEntities()
to check if a throttled entity group is classified as "stale". If it is, the entities are rescheduled using
QueuePriorityEnum::NORMAL
; otherwise, they keep their respective queue priority.
Metrics, Logging, and Environment Handling
Metrics, Logging, and Environment Handling
Added Environment Tracking:
Introduced a
$environment
property (defaulting to
'local'
) and a
setEnvironment()
method to dynamically assign it.
Memory Monitoring via Datadog Gauge:
Added a
logMemoryUsage()
method that submits peak memory allocation to Datadog as a gauge metric (
jiminny.reindex-memory-usage
).
Redis Lock for Metrics Throttling:
Implemented a Redis-backed lock (
%s-process-manager-lock
) with a 60-second expiration inside
logMemoryUsage()
to prevent over-reporting metrics.
Log Payload Update:
Added total processed items (
$updatedDocuments + $deletedDocuments
) to the final information log.
Good response
Bad response
Share & export
Copy
Show more options
You said are there any issues or potential bottlenecks
You said
are there any issues or potential bottlenecks
Copy prompt
Edit
Gemini said
Gemini said
Based on the code selection provided, there are several potential issues, bottlenecks, and architectural risks—primarily revolving around memory management, race conditions, and error handling.
Here is a breakdown of the potential issues to look out for:
1. Memory Leak Risk (OOM Crashes)
1. Memory Leak Risk (OOM Crashes)
Removed Restart Logic:
The most significant change is the removal of the
$counter
and
RESTART_AFTER
logic. Previously, the worker restarted itself to let the OS reclaim memory. Now, it relies entirely on PHP's internal garbage collection (
gc_collect_cycles()
).
Dependency Leaks:
If any underlying service (like the
ElasticSearchClient
, ORM, or Datadog helper) retains references to processed entities,
gc_collect_cycles()
will not be able to free them. The worker will eventually hit the memory limit and crash with a fatal Out Of Memory (OOM) error.
Hardcoded Limits:
The code still includes
ini_set('memory_limit', '512M');
. Hardcoding environment constraints inside application logic makes it difficult to scale or tune workers dynamically via infrastructure configurations.
2. Race Condition in Redis Lock
2. Race Condition in Redis Lock
Non-Atomic Operations:
The
logMemoryUsage
method uses three distinct steps to handle the lock:
get()
,
set()
, and
expire()
.
The Bottleneck:
If multiple workers process the same
$this->updateTarget
concurrently, they can both pass the
if (! is_null(Redis::get(...)))
check at the exact same millisecond. Both will then execute
set()
and push duplicate metrics to Datadog.
The Fix:
This should be replaced with a single atomic operation using Redis's
NX
(Not eXists) and
EX
(Expire) flags, or Laravel’s built-in atomic cache locks:
Redis::set($gaugeLockName, true, 'EX', 60, 'NX')
.
3. Infinite Loops and Caller Contract
3. Infinite Loops and Caller Contract
Return Value Change:
doUpdateEntitiesChunk()
previously returned whether it should iterate again based on staleness or chunk size. It now always returns
true
(unless the selection is totally empty).
The Bottleneck:
If the code calling this method relies on a
false
return to pause, sleep, or gracefully terminate, changing this to an unconditional
true
could result in an aggressive "busy loop" that spikes CPU usage.
4. Performance Overhead
4. Performance Overhead
Expensive Garbage Collection:
Calling
gc_collect_cycles();
on
every single chunk
can be CPU-intensive. While it is necessary since the restart logic was removed, it adds overhead to the loop.
Synchronous Metric Dispatch:
If the
Datadog::increment
or
Datadog::gauge
methods are making synchronous API calls over HTTP or TCP (rather than non-blocking UDP like standard StatsD), this will block the worker and significantly slow down the indexing process.
5. Lack of Explicit Error Handling
5. Lack of Explicit Error Handling
No
try/catch
Blocks:
The script executes complex network operations (
loadDocuments
, Datadog logging, ElasticSearch updates/deletes) without visible exception handling in this snippet. If a single document fails to load or the Elasticsearch cluster drops a connection, the unhandled exception will crash the worker mid-batch, potentially leaving the batch in an inconsistent state.
Good response
Bad response
Redo
Share & export
Copy
Show more options...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
64817
|
2275
|
11
|
2026-05-20T15:03:01.505590+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779289381505_m2.jpg...
|
Firefox
|
DXP4800PLUS-B5F8 — Personal
|
1
|
nas.lakylak.xyz/desktop/#/
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
[CircleCI] Workflow failed: jiminny / app on JY-20 [CircleCI] Workflow failed: jiminny / app on JY-20676-delete-report-related-objects - [EMAIL] - Gmail
[CircleCI] Workflow failed: jiminny / app on JY-20676-delete-report-related-objects - [EMAIL] - Gmail
DXP4800PLUS-B5F8
DXP4800PLUS-B5F8
Close tab
(191) TED LASSO BEST SCENES - PART 2. The Most Heartwarming Ted Lasso Show Moments. - YouTube
(191) TED LASSO BEST SCENES - PART 2. The Most Heartwarming Ted Lasso Show Moments. - YouTube
LinkedIn Login, Sign in | LinkedIn
LinkedIn Login, Sign in | LinkedIn
Ambulatory sheet - Paperless-ngx
Ambulatory sheet - Paperless-ngx
New Tab
New Tab
💬1 - Apple MacBook Pro 14" Space Black M5 Max | Laptop.bg - Технологията с теб
💬1 - Apple MacBook Pro 14" Space Black M5 Max | Laptop.bg - Технологията с теб
Apple Mac Studio M4 Max | Desktop.bg - Мощни PC Гейминг конфигурации, Персонални компютри на изплащане за игри и работа
Apple Mac Studio M4 Max | Desktop.bg - Мощни PC Гейминг конфигурации, Персонални компютри на изплащане за игри и работа
Welcome to authentik! - authentik
Welcome to authentik! - authentik
Photo - Google Photos
Photo - Google Photos
Завеждане на щета онлайн | Euroins
Завеждане на щета онлайн | Euroins
Google Maps
Google Maps
New Tab
New Tab
MAZANOKE | Online Image Optimizer That Runs Privately in Your Browser
MAZANOKE | Online Image Optimizer That Runs Privately in Your Browser
New Tab
New Tab
Електронно банкиране ДСК Директ от Банка ДСК
Електронно банкиране ДСК Директ от Банка ДСК
New Tab
Customize sidebar
Close Google Gemini (⌃X)
Open history (⇧⌘H)
Open bookmarks (⌘B)
Bitwarden
AI Chat settings
Close
Main menu
Gemini Pro...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"[CircleCI] Workflow failed: jiminny / app on JY-20676-delete-report-related-objects - kovaliklukas@gmail.com - Gmail","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[CircleCI] Workflow failed: jiminny / app on JY-20676-delete-report-related-objects - kovaliklukas@gmail.com - Gmail","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.20511968,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"DXP4800PLUS-B5F8","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"DXP4800PLUS-B5F8","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.036901597,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.05651596,"top":0.09177973,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"(191) TED LASSO BEST SCENES - PART 2. The Most Heartwarming Ted Lasso Show Moments. - YouTube","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"(191) TED LASSO BEST SCENES - PART 2. The Most Heartwarming Ted Lasso Show Moments. - YouTube","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.18218085,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"LinkedIn Login, Sign in | LinkedIn","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"LinkedIn Login, Sign in | LinkedIn","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.05668218,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Ambulatory sheet - Paperless-ngx","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Ambulatory sheet - Paperless-ngx","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.059507977,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"New Tab","depth":4,"bounds":{"left":0.0,"top":0.21548285,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"bounds":{"left":0.013297873,"top":0.22665602,"width":0.014960106,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"💬1 - Apple MacBook Pro 14\" Space Black M5 Max | Laptop.bg - Технологията с теб","depth":4,"bounds":{"left":0.0,"top":0.2482043,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"💬1 - Apple MacBook Pro 14\" Space Black M5 Max | Laptop.bg - Технологията с теб","depth":5,"bounds":{"left":0.013297873,"top":0.25937748,"width":0.14727394,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Apple Mac Studio M4 Max | Desktop.bg - Мощни PC Гейминг конфигурации, Персонални компютри на изплащане за игри и работа","depth":4,"bounds":{"left":0.0,"top":0.28092578,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Apple Mac Studio M4 Max | Desktop.bg - Мощни PC Гейминг конфигурации, Персонални компютри на изплащане за игри и работа","depth":5,"bounds":{"left":0.013297873,"top":0.29209897,"width":0.2365359,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Welcome to authentik! - authentik","depth":4,"bounds":{"left":0.0,"top":0.31364724,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Welcome to authentik! - authentik","depth":5,"bounds":{"left":0.013297873,"top":0.32482043,"width":0.05900931,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Photo - Google Photos","depth":4,"bounds":{"left":0.0,"top":0.3463687,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Photo - Google Photos","depth":5,"bounds":{"left":0.013297873,"top":0.3575419,"width":0.03956117,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Завеждане на щета онлайн | Euroins","depth":4,"bounds":{"left":0.0,"top":0.3790902,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Завеждане на щета онлайн | Euroins","depth":5,"bounds":{"left":0.013297873,"top":0.39026338,"width":0.0653258,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Google Maps","depth":4,"bounds":{"left":0.0,"top":0.41181165,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Google Maps","depth":5,"bounds":{"left":0.013297873,"top":0.42298484,"width":0.022772606,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"New Tab","depth":4,"bounds":{"left":0.0,"top":0.4445331,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"bounds":{"left":0.013297873,"top":0.4557063,"width":0.014960106,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"MAZANOKE | Online Image Optimizer That Runs Privately in Your Browser","depth":4,"bounds":{"left":0.0,"top":0.4772546,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"MAZANOKE | Online Image Optimizer That Runs Privately in Your Browser","depth":5,"bounds":{"left":0.013297873,"top":0.4884278,"width":0.12749335,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"New Tab","depth":4,"bounds":{"left":0.0,"top":0.509976,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"bounds":{"left":0.013297873,"top":0.5211492,"width":0.014960106,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Електронно банкиране ДСК Директ от Банка ДСК","depth":4,"bounds":{"left":0.0,"top":0.54269755,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Електронно банкиране ДСК Директ от Банка ДСК","depth":5,"bounds":{"left":0.013297873,"top":0.55387074,"width":0.09059176,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.57701516,"width":0.06333112,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bitwarden","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"bounds":{"left":0.21941489,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"bounds":{"left":0.23138298,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Main menu","depth":13,"bounds":{"left":0.071476065,"top":0.09736632,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Gemini Pro","depth":14,"bounds":{"left":0.08743351,"top":0.10694334,"width":0.040392287,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
-2561635208211412717
|
5538315868906819247
|
click
|
accessibility
|
NULL
|
[CircleCI] Workflow failed: jiminny / app on JY-20 [CircleCI] Workflow failed: jiminny / app on JY-20676-delete-report-related-objects - [EMAIL] - Gmail
[CircleCI] Workflow failed: jiminny / app on JY-20676-delete-report-related-objects - [EMAIL] - Gmail
DXP4800PLUS-B5F8
DXP4800PLUS-B5F8
Close tab
(191) TED LASSO BEST SCENES - PART 2. The Most Heartwarming Ted Lasso Show Moments. - YouTube
(191) TED LASSO BEST SCENES - PART 2. The Most Heartwarming Ted Lasso Show Moments. - YouTube
LinkedIn Login, Sign in | LinkedIn
LinkedIn Login, Sign in | LinkedIn
Ambulatory sheet - Paperless-ngx
Ambulatory sheet - Paperless-ngx
New Tab
New Tab
💬1 - Apple MacBook Pro 14" Space Black M5 Max | Laptop.bg - Технологията с теб
💬1 - Apple MacBook Pro 14" Space Black M5 Max | Laptop.bg - Технологията с теб
Apple Mac Studio M4 Max | Desktop.bg - Мощни PC Гейминг конфигурации, Персонални компютри на изплащане за игри и работа
Apple Mac Studio M4 Max | Desktop.bg - Мощни PC Гейминг конфигурации, Персонални компютри на изплащане за игри и работа
Welcome to authentik! - authentik
Welcome to authentik! - authentik
Photo - Google Photos
Photo - Google Photos
Завеждане на щета онлайн | Euroins
Завеждане на щета онлайн | Euroins
Google Maps
Google Maps
New Tab
New Tab
MAZANOKE | Online Image Optimizer That Runs Privately in Your Browser
MAZANOKE | Online Image Optimizer That Runs Privately in Your Browser
New Tab
New Tab
Електронно банкиране ДСК Директ от Банка ДСК
Електронно банкиране ДСК Директ от Банка ДСК
New Tab
Customize sidebar
Close Google Gemini (⌃X)
Open history (⇧⌘H)
Open bookmarks (⌘B)
Bitwarden
AI Chat settings
Close
Main menu
Gemini Pro...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
64818
|
2274
|
10
|
2026-05-20T15:03:00.381215+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779289380381_m1.jpg...
|
Firefox
|
DXP4800PLUS-B5F8 — Personal
|
1
|
nas.lakylak.xyz/desktop/#/
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
SlackFileEditViewGoHistoryWindowHelp> 0 lll20A1 SlackFileEditViewGoHistoryWindowHelp> 0 lll20A100% C4 8• Wed 20 May 18:02:59DOCKERO ₴1DEV (-zsh)APP (-zsh)|₴2181screenpipe"О ₴3APP (-zsh)Fixed 0 of 5690 files in 78.144 seconds, 60.00MBmemoryusedWhat's next:Try Docker Debug for seamless, persistentdebugging tools in any containeror image → docker debug docker_lamp_1Learn more at [URL_WITH_CREDENTIALS] (JY-20676-delete-report-related-objects) $ sips -s format png ~/Downloads/Photos-3-001\(2\)/talon-f.HEIC --out ~/Downloads/Photos-3-001\(2\)/talon-f.png/Users/lukas/Downloads/Photos-3-001(2)/talon-f.HEIC/Users/lukas/Downloads/Photos-3-001(2)/talon-f.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app(JY-20676-delete-report-related-objects) $ sips -s format png ~/Downloads/Photos-3-001\(2\)/talon-b.HEIC --out ~/Downloads/Photos-3-001\(2\)/talon-b.png/Users/lukas/Downloads/Photos-3-001(2)/talon-b.HEIC/Users/lukas/Downloads/Photos-3-001(2)/talon-b.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app(JY-20676-delete-report-related-objects) $ sips -s format png ~/Downloads/Photos-3-001\(2\)/vin.HEIC --out ~/Downloads/Photos-3-001\(2\)/vin.png/Users/lukas/Downloads/Photos-3-001(2)/vin.HEIC/Users/lukas/Downloads/Photos-3-001(2)/vin.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-objects) $ sips -s format png ~/Downloads/Photos-3-001\(2\)/dr-lic-f.HEIC --out ~/Downloads/Photos-3-001\(2\)/dr-lic-f.png/Users/lukas/Downloads/Photos-3-001(2)/dr-lic-f.HEIC/Users/lukas/Downloads/Photos-3-001(2)/dr-lic-f.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-objects) $ sips -s format png ~/Downloads/Photos-3-001\(2\)/dr-lic-b.HEIC --out ~/Downloads/Photos-3-001\(2\)/dr-lic-b.png/Users/lukas/Downloads/Photos-3-001(2)/dr-lic-b.HEIC/Users/lukas/Downloads/Photos-3-001(2)/dr-lic-b.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-objects)sips -s format png ~/Downloads/Photos-3-001\(2\)/gtp.HEIC --out ~/Downloads/Photos-3-001\(2)/gtp.pngWarning: /Users/lukas/Downloads/Photos-3-001(2)/gtp.HEIC not a valid file - skippinglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-objects) $ sips -s format png ~/Downloads/Photos-3-001\(2\)/gtp.heif --out ~/Downloads/Photos-3-001\(2\)/gtp.png/Users/lukas/Downloads/Photos-3-001(2)/gtp.heif/Users/lukas/Downloads/Photos-3-001(2)/gtp.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-objects) $ sips -s format png ~/Downloads/Photos-3-001\(2\)/talon-m-f.HEIC --out ~/Downloads/Photos-3-001\(2\)/talon-m-f.png/Users/lukas/Downloads/Photos-3-001(2)/talon-m-f.HEIC/Users/lukas/Downloads/Photos-3-001(2)/talon-m-f.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/Jiminny/app(JY-20676-delete-report-related-objects) $ sips -s format png ~/Downloads/Photos-3-001\(2\)/talon-m-b.HEIC --out ~/Downloads/Photos-3-001\(2\)/talon-m-b.png/Users/lukas/Downloads/Photos-3-001(2)/talon-m-b.HEIC/Users/lukas/Downloads/Photos-3-001(2)/talon-m-b.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-objects) $D...
|
NULL
|
8955689368528018550
|
NULL
|
click
|
ocr
|
NULL
|
SlackFileEditViewGoHistoryWindowHelp> 0 lll20A1 SlackFileEditViewGoHistoryWindowHelp> 0 lll20A100% C4 8• Wed 20 May 18:02:59DOCKERO ₴1DEV (-zsh)APP (-zsh)|₴2181screenpipe"О ₴3APP (-zsh)Fixed 0 of 5690 files in 78.144 seconds, 60.00MBmemoryusedWhat's next:Try Docker Debug for seamless, persistentdebugging tools in any containeror image → docker debug docker_lamp_1Learn more at [URL_WITH_CREDENTIALS] (JY-20676-delete-report-related-objects) $ sips -s format png ~/Downloads/Photos-3-001\(2\)/talon-f.HEIC --out ~/Downloads/Photos-3-001\(2\)/talon-f.png/Users/lukas/Downloads/Photos-3-001(2)/talon-f.HEIC/Users/lukas/Downloads/Photos-3-001(2)/talon-f.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app(JY-20676-delete-report-related-objects) $ sips -s format png ~/Downloads/Photos-3-001\(2\)/talon-b.HEIC --out ~/Downloads/Photos-3-001\(2\)/talon-b.png/Users/lukas/Downloads/Photos-3-001(2)/talon-b.HEIC/Users/lukas/Downloads/Photos-3-001(2)/talon-b.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app(JY-20676-delete-report-related-objects) $ sips -s format png ~/Downloads/Photos-3-001\(2\)/vin.HEIC --out ~/Downloads/Photos-3-001\(2\)/vin.png/Users/lukas/Downloads/Photos-3-001(2)/vin.HEIC/Users/lukas/Downloads/Photos-3-001(2)/vin.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-objects) $ sips -s format png ~/Downloads/Photos-3-001\(2\)/dr-lic-f.HEIC --out ~/Downloads/Photos-3-001\(2\)/dr-lic-f.png/Users/lukas/Downloads/Photos-3-001(2)/dr-lic-f.HEIC/Users/lukas/Downloads/Photos-3-001(2)/dr-lic-f.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-objects) $ sips -s format png ~/Downloads/Photos-3-001\(2\)/dr-lic-b.HEIC --out ~/Downloads/Photos-3-001\(2\)/dr-lic-b.png/Users/lukas/Downloads/Photos-3-001(2)/dr-lic-b.HEIC/Users/lukas/Downloads/Photos-3-001(2)/dr-lic-b.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-objects)sips -s format png ~/Downloads/Photos-3-001\(2\)/gtp.HEIC --out ~/Downloads/Photos-3-001\(2)/gtp.pngWarning: /Users/lukas/Downloads/Photos-3-001(2)/gtp.HEIC not a valid file - skippinglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-objects) $ sips -s format png ~/Downloads/Photos-3-001\(2\)/gtp.heif --out ~/Downloads/Photos-3-001\(2\)/gtp.png/Users/lukas/Downloads/Photos-3-001(2)/gtp.heif/Users/lukas/Downloads/Photos-3-001(2)/gtp.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-objects) $ sips -s format png ~/Downloads/Photos-3-001\(2\)/talon-m-f.HEIC --out ~/Downloads/Photos-3-001\(2\)/talon-m-f.png/Users/lukas/Downloads/Photos-3-001(2)/talon-m-f.HEIC/Users/lukas/Downloads/Photos-3-001(2)/talon-m-f.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/Jiminny/app(JY-20676-delete-report-related-objects) $ sips -s format png ~/Downloads/Photos-3-001\(2\)/talon-m-b.HEIC --out ~/Downloads/Photos-3-001\(2\)/talon-m-b.png/Users/lukas/Downloads/Photos-3-001(2)/talon-m-b.HEIC/Users/lukas/Downloads/Photos-3-001(2)/talon-m-b.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-objects) $D...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
64819
|
2274
|
11
|
2026-05-20T15:03:05.733189+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779289385733_m1.jpg...
|
Firefox
|
DXP4800PLUS-B5F8 — Personal
|
1
|
nas.lakylak.xyz/desktop/#/
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
SlackFileEditViewGoHistoryWindowHelp> 0 lll20A1 SlackFileEditViewGoHistoryWindowHelp> 0 lll20A100% C4 8• Wed 20 May 18:03:05DOCKERO ₴1DEV (-zsh)APP (-zsh)|₴2181screenpipe"О ₴3APP (-zsh)Fixed 0 of 5690 files in 78.144 seconds, 60.00MBmemoryusedWhat's next:Try Docker Debug for seamless, persistentdebugging tools in any containeror image → docker debug docker_lamp_1Learn more at [URL_WITH_CREDENTIALS] (JY-20676-delete-report-related-objects) $ sips -s format png ~/Downloads/Photos-3-001\(2\)/talon-f.HEIC --out ~/Downloads/Photos-3-001\(2\)/talon-f.png/Users/lukas/Downloads/Photos-3-001(2)/talon-f.HEIC/Users/lukas/Downloads/Photos-3-001(2)/talon-f.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app(JY-20676-delete-report-related-objects) $ sips -s format png ~/Downloads/Photos-3-001\(2\)/talon-b.HEIC --out ~/Downloads/Photos-3-001\(2\)/talon-b.png/Users/lukas/Downloads/Photos-3-001(2)/talon-b.HEIC/Users/lukas/Downloads/Photos-3-001(2)/talon-b.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app(JY-20676-delete-report-related-objects) $ sips -s format png ~/Downloads/Photos-3-001\(2\)/vin.HEIC --out ~/Downloads/Photos-3-001\(2\)/vin.png/Users/lukas/Downloads/Photos-3-001(2)/vin.HEIC/Users/lukas/Downloads/Photos-3-001(2)/vin.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-objects) $ sips -s format png ~/Downloads/Photos-3-001\(2\)/dr-lic-f.HEIC --out ~/Downloads/Photos-3-001\(2\)/dr-lic-f.png/Users/lukas/Downloads/Photos-3-001(2)/dr-lic-f.HEIC/Users/lukas/Downloads/Photos-3-001(2)/dr-lic-f.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-objects) $ sips -s format png ~/Downloads/Photos-3-001\(2\)/dr-lic-b.HEIC --out ~/Downloads/Photos-3-001\(2\)/dr-lic-b.png/Users/lukas/Downloads/Photos-3-001(2)/dr-lic-b.HEIC/Users/lukas/Downloads/Photos-3-001(2)/dr-lic-b.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-objects)sips -s format png ~/Downloads/Photos-3-001\(2\)/gtp.HEIC --out ~/Downloads/Photos-3-001\(2)/gtp.pngWarning: /Users/lukas/Downloads/Photos-3-001(2)/gtp.HEIC not a valid file - skippinglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-objects) $ sips -s format png ~/Downloads/Photos-3-001\(2\)/gtp.heif --out ~/Downloads/Photos-3-001\(2\)/gtp.png/Users/lukas/Downloads/Photos-3-001(2)/gtp.heif/Users/lukas/Downloads/Photos-3-001(2)/gtp.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-objects) $ sips -s format png ~/Downloads/Photos-3-001\(2\)/talon-m-f.HEIC --out ~/Downloads/Photos-3-001\(2\)/talon-m-f.png/Users/lukas/Downloads/Photos-3-001(2)/talon-m-f.HEIC/Users/lukas/Downloads/Photos-3-001(2)/talon-m-f.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/Jiminny/app(JY-20676-delete-report-related-objects) $ sips -s format png ~/Downloads/Photos-3-001\(2\)/talon-m-b.HEIC --out ~/Downloads/Photos-3-001\(2\)/talon-m-b.png/Users/lukas/Downloads/Photos-3-001(2)/talon-m-b.HEIC/Users/lukas/Downloads/Photos-3-001(2)/talon-m-b.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-objects) $D...
|
NULL
|
2407185072813990939
|
NULL
|
click
|
ocr
|
NULL
|
SlackFileEditViewGoHistoryWindowHelp> 0 lll20A1 SlackFileEditViewGoHistoryWindowHelp> 0 lll20A100% C4 8• Wed 20 May 18:03:05DOCKERO ₴1DEV (-zsh)APP (-zsh)|₴2181screenpipe"О ₴3APP (-zsh)Fixed 0 of 5690 files in 78.144 seconds, 60.00MBmemoryusedWhat's next:Try Docker Debug for seamless, persistentdebugging tools in any containeror image → docker debug docker_lamp_1Learn more at [URL_WITH_CREDENTIALS] (JY-20676-delete-report-related-objects) $ sips -s format png ~/Downloads/Photos-3-001\(2\)/talon-f.HEIC --out ~/Downloads/Photos-3-001\(2\)/talon-f.png/Users/lukas/Downloads/Photos-3-001(2)/talon-f.HEIC/Users/lukas/Downloads/Photos-3-001(2)/talon-f.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app(JY-20676-delete-report-related-objects) $ sips -s format png ~/Downloads/Photos-3-001\(2\)/talon-b.HEIC --out ~/Downloads/Photos-3-001\(2\)/talon-b.png/Users/lukas/Downloads/Photos-3-001(2)/talon-b.HEIC/Users/lukas/Downloads/Photos-3-001(2)/talon-b.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app(JY-20676-delete-report-related-objects) $ sips -s format png ~/Downloads/Photos-3-001\(2\)/vin.HEIC --out ~/Downloads/Photos-3-001\(2\)/vin.png/Users/lukas/Downloads/Photos-3-001(2)/vin.HEIC/Users/lukas/Downloads/Photos-3-001(2)/vin.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-objects) $ sips -s format png ~/Downloads/Photos-3-001\(2\)/dr-lic-f.HEIC --out ~/Downloads/Photos-3-001\(2\)/dr-lic-f.png/Users/lukas/Downloads/Photos-3-001(2)/dr-lic-f.HEIC/Users/lukas/Downloads/Photos-3-001(2)/dr-lic-f.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-objects) $ sips -s format png ~/Downloads/Photos-3-001\(2\)/dr-lic-b.HEIC --out ~/Downloads/Photos-3-001\(2\)/dr-lic-b.png/Users/lukas/Downloads/Photos-3-001(2)/dr-lic-b.HEIC/Users/lukas/Downloads/Photos-3-001(2)/dr-lic-b.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-objects)sips -s format png ~/Downloads/Photos-3-001\(2\)/gtp.HEIC --out ~/Downloads/Photos-3-001\(2)/gtp.pngWarning: /Users/lukas/Downloads/Photos-3-001(2)/gtp.HEIC not a valid file - skippinglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-objects) $ sips -s format png ~/Downloads/Photos-3-001\(2\)/gtp.heif --out ~/Downloads/Photos-3-001\(2\)/gtp.png/Users/lukas/Downloads/Photos-3-001(2)/gtp.heif/Users/lukas/Downloads/Photos-3-001(2)/gtp.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-objects) $ sips -s format png ~/Downloads/Photos-3-001\(2\)/talon-m-f.HEIC --out ~/Downloads/Photos-3-001\(2\)/talon-m-f.png/Users/lukas/Downloads/Photos-3-001(2)/talon-m-f.HEIC/Users/lukas/Downloads/Photos-3-001(2)/talon-m-f.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/Jiminny/app(JY-20676-delete-report-related-objects) $ sips -s format png ~/Downloads/Photos-3-001\(2\)/talon-m-b.HEIC --out ~/Downloads/Photos-3-001\(2\)/talon-m-b.png/Users/lukas/Downloads/Photos-3-001(2)/talon-m-b.HEIC/Users/lukas/Downloads/Photos-3-001(2)/talon-m-b.pnglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-objects) $D...
|
64818
|
NULL
|
NULL
|
NULL
|