chore: analyze sendportal_subscribers performance#3
Draft
faridatulna wants to merge 1 commit intomasterfrom
Draft
chore: analyze sendportal_subscribers performance#3faridatulna wants to merge 1 commit intomasterfrom
faridatulna wants to merge 1 commit intomasterfrom
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Performance Analysis:
sendportal_subscribersTableOverview
This document records the findings of a deep-dive query analysis on the
sendportal_subscriberstable.It covers schema/index gaps, slow query patterns, unnecessary queries, and a prioritised improvement plan
with exact file references.
1. Current Schema & Indexes
Migration:
sendportal-core/database/migrations/2017_04_28_223840_create_subscribers_table.phpidworkspace_idhashemailfirst_namelast_namemetaunsubscribed_atunsubscribe_event_idcreated_atupdated_atRelated pivot table
sendportal_tag_subscriberalready has:idx_tag_subscriberon(tag_id, subscriber_id)idx_subscriber_tagon(subscriber_id, tag_id)2. Issues Found
P0 ? Critical
2.1 Missing
workspace_idfilter in bulk sync (security + correctness)File:
sendportal-core/src/Http/Controllers/Api/SubscribersController.php:107The initial email lookup in the
sync()endpoint has noworkspace_idfilter.This can pull subscribers from other workspaces, leaking data across tenants.
The follow-up query at line 160 does add the scope, making the pair inconsistent.
Fix:
2.2 Unbounded
->get()on full exportFile:
sendportal-core/src/Http/Controllers/Subscribers/SubscribersController.php:157Called on every CSV export request. A workspace with tens of thousands of subscribers
will either time out or exhaust PHP memory.
Fix: Use a database cursor to stream rows one at a time:
Or chunk the output with
chunkById()and stream the response incrementally.2.3 Full-wildcard
LIKEsearch ? guaranteed full table scanFile:
sendportal-core/src/Repositories/Subscribers/BaseSubscriberTenantRepository.php:115-118A leading
%wildcard prevents the query planner from using any index.Every search request forces a full scan of the subscribers table.
Fix options (choose one based on requirements):
'text%'emailindex; loses mid-string matchingFULLTEXTindex on(first_name, last_name, email)and useMATCH ? AGAINSTP1 ? High
2.4 N+1 queries in CSV import loop
File:
sendportal-core/src/Services/Subscribers/ImportSubscriberService.php:28-43For every row in the import file, the loop issues:
findBy(id)? one queryfindBy(email)if the ID lookup missed ? second querytagsrelationship on the resultA 1,000-row CSV triggers up to 3,000 queries.
Fix: Batch-fetch all emails before the loop, then resolve from memory:
2.5 Missing composite indexes
MySQL cannot combine two separate single-column indexes for a multi-column
WHEREclause.All of the queries below currently cause MySQL to pick one index and filter the rest in memory.
countActive()BaseSubscriberTenantRepository.php:81workspace_id AND unsubscribed_at IS NULLgetRecentSubscribers()BaseSubscriberTenantRepository.php:88workspace_id ORDER BY created_at DESCgetGrowthChartData()MySqlSubscriberTenantRepository.php:16workspace_id AND unsubscribed_at AND created_atstoreOrUpdate()ApiSubscriberService.php:30workspace_id AND emailRecommended migration:
2.6 Duplicate tag query in
edit()actionFile:
sendportal-core/src/Http/Controllers/Subscribers/SubscribersController.php:102The edit action loads the subscriber (with relationships), then issues a second query
to pluck tag IDs ? even though the tags collection is already on the loaded model.
Fix: Replace the second query with the already-loaded relationship:
P2 ? Medium
2.7
GROUP BYon a timestamp function bypasses indexFiles:
sendportal-core/src/Repositories/Subscribers/MySqlSubscriberTenantRepository.php:26,34sendportal-core/src/Repositories/Subscribers/PostgresSubscriberTenantRepository.php(same pattern)Wrapping a column in a function prevents the query planner from using the index on that column.
Fix (MySQL): Use
DATE(created_at)and add a generated column index if the dashboardquery runs frequently:
2.8 No pagination on tag/subscriber relationship endpoints
Files:
sendportal-core/src/Http/Controllers/Api/TagSubscribersController.php:40sendportal-core/src/Http/Controllers/Api/SubscriberTagsController.php:40Both endpoints eager-load the entire relationship with no limit.
A tag with 100,000 subscribers returns all rows in a single response.
Fix: Add
paginate()to both endpoints and document theper_pageparameter in the API.3. Improvement Plan (Prioritised)
workspace_idin syncApi/SubscribersController.php:107.where('workspace_id', $workspaceId)->get()Subscribers/SubscribersController.php:157BaseSubscriberTenantRepository.php:115ImportSubscriberService.php:28edit()Subscribers/SubscribersController.php:102GROUP BYon timestamp functionMySqlSubscriberTenantRepository.php:26,34DATE()+ generated column indexApi/TagSubscribersController.php:40paginate()