53 KiB
Skinbase.org — Optimization Discovery Audit
Audit type: read-only discovery (no application, schema, or configuration changes)
Date: 2026-06-05
Repository: D:\Sites\Skinbase26
Branch: develop
Commit: 5af95f6533a129e6c22f051c632ab821240e8317 (5af95f65 Optimize academy)
Working tree at start: already dirty (many modified and untracked files). Those files were not reset, stashed, or cleaned. This audit inspected the on-disk tree as it existed, including dirty work.
1. Executive Summary
Backend: Laravel 12.61.1, PHP ^8.2 (composer platform hints 8.4), Horizon 5.47.2, Reverb 1.10.2
Frontend: Hybrid MPA: Blade (Nova homepage/gallery) + Inertia React 19 + Vite 7 SSR
Database: MySQL (env example: DB_CONNECTION=mysql, DB_DATABASE=skinbase26); legacy MySQL also configured
Cache: default CACHE_STORE=database; homepage uses failover store redis→database→array
Sessions: SESSION_DRIVER=database, 120 min; ConditionalStartSession skips anonymous public GET
Queues: QUEUE_CONNECTION=redis (.env.example); Horizon + supervisor/systemd examples
Search: Laravel Scout 10.25 + Meilisearch PHP 1.16; SQL LIKE fallbacks remain (news search)
Storage: local disks + S3-compatible object storage for artworks; originals also on local/backup roots
CDN: FILES_CDN_URL / AVATAR_CDN_URL default https://cdn.skinbase.org; Cloudflare purge optional
Rendering: Blade for public homepage/browse/search; Inertia+SSR for studio/admin/profile/upload/feed
Approx. size: ~1361 PHP in app/, 511 JSX, 248 Blade views, 308 migrations, 376 services, 317 controllers, 205 models
Primary content: Public artworks (skins, wallpapers, photography, digital art) plus academy, forum, feed, collections
Primary suspected bottleneck areas:
1) High-write counters (views/downloads) and scheduled ranking/metric scans
2) Default database cache/session vs Redis already used for queues
3) Media pipeline (sync derivatives by default) and PHP-streamed downloads unless nginx accel is live
4) Search/filter URL surfaces + sitemap live-build fallback
5) Queue worker timeout 90s vs recommendation jobs timeout 900s
Audit confidence: HIGH for architecture/code paths; MEDIUM for production runtime (no live server/.env); HIGH for historical slow-query evidence in docs/slow-query-optimization-plan.md
P0 count: 4
P1 count: 16
P2 count: 12
P3 count: 8
This audit does not recommend an implementation order. It classifies evidence so a later milestone can design a measured roadmap.
2. Repository State
| Item | Value |
|---|---|
| Branch | develop (up to date with origin/develop) |
| HEAD | 5af95f6533a129e6c22f051c632ab821240e8317 |
| Last commit | 5af95f65 Optimize academy |
| Working tree before audit | Dirty |
| Audit modifications to app | None |
Dirty at start (non-exhaustive; git status at start of audit):
- Modified: sitemap pipeline, academy controllers/views, SearchController, Artwork model/observers, vision vector search, composer/package.json, robots.txt, routes, tests, SSR bootstrap, featured thumbnail generator.
- Deleted:
public/sitemap.xml - Untracked: security-report feature, academy pages sitemap builder, featured thumbnail audit migration, nginx search-rate-limit.conf, admin system pages.
Safety: the audit did not reset, stash, checkout, or revert any of that work.
3. Architecture
Architecture map (as coded)
Browser
↓
nginx (repo snippets: static-cache, sitemaps, download-accel, search-rate-limit, upstream-error-pages)
├── hashed /build/assets (1y Cache-Control)
├── public/sitemaps/*.xml (try_files then PHP)
└── PHP-FPM (unknown production pool)
↓
Laravel 12
├── MySQL (artworks, users, stats, sessions, cache table, ControlPanel)
├── Redis (queues, Horizon, presence, optional homepage cache, artwork_stats deltas)
├── Meilisearch (Scout indexes: artworks, users, groups, posts, messages)
├── Object storage / CDN (cdn.skinbase.org, S3 disk)
├── Vision / CLIP / YOLO / vector gateway (optional HTTP)
├── Stripe / Cashier (Academy billing; disabled by default in .env.example)
├── Sentry
├── Reverb + Echo (messaging)
└── Queue workers (Horizon supervisors + deploy/supervisor example)
Node SSR (Inertia) on 127.0.0.1:13714 [config/inertia.php]
Python enhance-worker (services/enhance-worker) optional
Web request lifecycle
- nginx (if snippets are included in production vhost — UNKNOWN whether live).
public/index.php→ Laravel 12 bootstrap.- Web middleware replacements:
ConditionalStartSession,ConditionalShareErrorsFromSession,ConditionalValidateCsrfToken. - Appended:
SecurityHeaders,RedirectLegacyProfileSubdomain,TrackOnlineVisitor,UpdateLastVisit,HandleInertiaRequests, onboarding/email-upgrade middleware. - Route → controller/service → Blade or Inertia.
- Guest homepage sets
Cache-Control: public, max-age=60, s-maxage=300, stale-while-revalidate=600. Authenticated responses setprivate, no-store.
Confirmed vs unknown
| Layer | Status |
|---|---|
| Application architecture | CONFIRMED from repo |
| Production nginx/PHP-FPM/OPcache | UNKNOWN — only snippets in deploy/nginx/ |
Production .env drivers |
UNKNOWN — .env.example only |
| Historical MySQL slow log | CONFIRMED in docs/slow-query-optimization-plan.md (server3 / db skinbase, 2026-04-04→04-26) |
4. Technology Stack
| Area | Evidence | Version / default |
|---|---|---|
| PHP | composer.json require |
^8.2; platform ext-pcntl/posix 8.4.0 |
| Laravel | composer.lock |
v12.61.1 |
| Inertia | lock | inertia-laravel v1.3.4; JS @inertiajs/react ^1.0.4 |
| Horizon | lock | v5.47.2 |
| Reverb | lock | v1.10.2 |
| Scout | lock | v10.25.0 |
| Meilisearch PHP | lock | v1.16.1 |
| Intervention Image | lock | 3.11.8 |
| Predis | lock | v3.5.0; .env.example REDIS_CLIENT=phpredis |
| Sentry | lock | 4.25.1 |
| Cashier | composer.json | ^16.5 |
| Debugbar | require-dev | fruitcake/laravel-debugbar v4.3.0 |
| Pest | require-dev | ^4.3 |
| Node/Vite | package.json | Vite ^7.0.7, React ^19.2.4, Tailwind ^3.1.0 (also @tailwindcss/vite ^4) |
| Package manager | lockfiles | Composer + npm (package-lock.json) |
| CSS | resources | Tailwind + SCSS nova.scss + nova-grid.css |
| Auth | Breeze-style + Socialite | session guard web; extra controlpanel guard |
| Roles | middleware aliases | admin/moderator/staff/creator |
.env.example |
MAIL_MAILER=log |
|
| Analytics | code | internal academy/feed/discovery events; no GA package found in composer |
| Error monitoring | Sentry Integration in bootstrap/app.php |
DSN from env |
| Realtime | Reverb + Echo + pusher-js | messaging |
| ControlPanel | packages/klevze |
~1436 PHP files, ~510 /cp routes |
| Legacy site | oldSite/ |
historical PHP/assets; not the live app |
No Docker Compose / GitHub Actions found at repo root.
5. Application Domains
Domains that exist in code (not assumed):
- Artworks / gallery / downloads / views / favourites / likes / reactions / awards / medals
- Categories / content types / tags
- Users / profiles / avatars / covers / XP / achievements / followers
- Discover / explore / ranking / heat / trending / for-you / recommendations / CLIP vectors
- Collections / groups / worlds / web stories
- Studio / dashboard / upload v1+v2 / enhance
- Comments (artwork, profile, news, collection, nova cards, posts)
- Forum
- Feed / posts / hashtags
- News / blog / pages / interviews
- Academy (courses, lessons, prompts, packs, challenges, billing)
- Nova Cards
- Messaging (Reverb)
- Search (Meilisearch + SQL)
- Sitemaps / robots / SEO
- Moderation / staff / traffic online visitors
- ControlPanel (
/cp) - Security reports (untracked at audit time)
Size inventory (application tree, excluding vendor/node_modules)
| Kind | Count |
|---|---|
PHP app/ |
1361 |
Blade resources/views |
248 |
PHP database/ |
330 |
| PHP tests | 274 |
| Config PHP | 63 |
| JSX | 511 |
JS in resources/js |
50 |
| Controllers | 317 |
| Models | 205 |
| Services | 376 |
| Jobs | 45 |
| Listeners | 6 |
| Commands | 124 |
| Middleware | 24 |
| Policies | 17 |
| Events | 33 |
| Notifications | 13 |
| Observers | 10 |
| Repositories | 3 |
| 6 | |
| HTTP Resources | 2 |
| React pages | 207 |
| React components | 288 |
| Blade components | 29 |
| Migrations | 308 |
| Feature tests | 227 |
| Unit tests | 45 |
| Playwright e2e | 11 |
| Packages PHP | 1436 |
| public/build JS | 260 |
| public/build CSS | 5 |
6. Route Inventory
php artisan route:list --json succeeded. var/routes.json (cached dump) reports 1518 routes.
Approximate mix from var/routes.json:
| Bucket | Approx count | Notes |
|---|---|---|
| GET|HEAD | 770 | |
| POST | 572 | |
| Auth-ish middleware | 669 | |
api/* |
303 | Many still use web middleware group |
cp ControlPanel |
510 | Dominant admin surface |
studio |
155 | |
settings |
75 | |
moderation / admin |
included in admin-ish 569 | |
_debugbar |
5 | Present in this dump — debug tooling registered in this environment |
| Horizon | 22 |
Source-level Route:: calls: web.php ~660, api.php ~272, auth.php 27, legacy.php 34.
Important public page types (benchmark candidates)
| Page Type | Example Route | Handler | Public? | Cache candidate? |
|---|---|---|---|---|
| Homepage | GET / |
Web\HomeController@index |
yes | already guest flexible cache |
| Homepage alias | GET /home |
same | yes | duplicate URL |
| Featured | GET /featured |
FeaturedArtworksController |
yes | yes |
| Latest | GET /uploads/latest |
Community\LatestController |
yes | yes |
| Discover trending | GET /discover/trending |
DiscoverController@trending |
yes | Meili + Cache::remember |
| Discover rising | GET /discover/rising |
DiscoverController@rising |
yes | yes |
| Discover fresh | GET /discover/fresh |
DiscoverController@fresh |
yes | yes |
| Explore | GET /explore |
ExploreController@index |
yes | Meili TTL map |
| Content-type browse | GET /{contentTypeSlug}/{path?} |
BrowseGalleryController@content |
yes | versioned cache |
| Artwork detail | GET /art/{id}/{slug?} |
ArtworkPageController@show |
yes | per-id candidate |
| Alternate artwork URL | GET /{type}/{categoryPath}/{artwork} |
BrowseGalleryController@showArtwork |
yes | duplicate of /art/{id} |
| Download | GET /download/artwork/{id} |
ArtworkDownloadController |
yes | no (write + file) |
| Search | GET /search |
Web\SearchController@index |
yes | Meili + news LIKE; noindex |
| Tags index | GET /tags |
Web\TagController@index |
yes | yes |
| Tag show | GET /tags/{tag} (two route shapes) |
TagController vs HashtagFeed | yes | collision risk |
| Categories | GET /categories |
CategoryController@index |
yes | yes |
| Profile | GET /@{username} |
ProfileController@showByUsername |
yes | per-user |
| Sitemap | GET /sitemap.xml |
SitemapController@index |
yes | static file preferred |
| Robots | GET /robots.txt |
RobotsTxtController |
yes | generated |
| For-you | GET /discover/for-you |
auth | no | personalized |
7. High-Traffic Request Paths
Homepage GET /
Route / (web)
→ ConditionalStartSession (skip anonymous GET if configured)
→ TrackOnlineVisitor (Redis presence)
→ HomeController::index
→ HomepageService::all() [guest] or allForUser($user)
→ view web.home (Blade, not Inertia)
- Guest:
Cache::store(homepage failover)->flexible(...)TTL default 1800s, fresh 30s (HomepageService,config/homepage.php). - Authenticated: no payload cache; personalization + same public rails.
- Sections: hero, announcement, community favorites, hall of fame, rising, trending, fresh, collections, world spotlight, groups, tags, creators, news.
- Cache-Control set on response (guest public, user no-store).
- Obvious DB/Meili sources: many (each section method); mitigated for guests by payload cache.
- External APIs: none on this path unless world/news fail internally.
- CONFIRMED cache candidate already implemented for guests. Authenticated homepage is still a potential cache candidate (partial).
Browse / explore / discover
- Discover trending:
ArtworkSearchService::discoverTrending(Meili cached) with SQL fallbackfallbackTrendingFromDatabase. - Explore/browse: Meilisearch sort maps +
Cache::rememberkeyed by sort/page/viewer segment (BrowseGalleryControllerCACHE_VERSIONv4). - Content-type catch-all
/{contentTypeSlug}/{path?}is a high-crawl surface.
Artwork detail GET /art/{id}/{slug?}
ArtworkPageController::show
→ Artwork::withTrashed lookup
→ 404/410/403 suggestion queries (ErrorSuggestionService)
→ full with([user.profile, group..., categories..., tags, stats, awardStat])
→ 301 if slug mismatch
→ ArtworkResource (extra Schema::hasTable + per-request counts)
→ related query with orWhereHas(categories|tags) limit 12
→ ArtworkComment::limit(500) with user.profile
→ Blade/Inertia mix (seo via SeoFactory::artwork)
Query sources: 2 artwork loads + related orWhereHas + comments up to 500 + resource extras (followers, likes, bookmarks, favourites).
View increment is not on this GET; client POSTs /api/art/{id}/view.
Profile GET /@{username}
ProfileController::renderProfilePage: artworks pagination, featured join artwork_features, favourites, user_statistics, social links, plus later tabs. Multiple queries; some eager loads present.
Search GET /search (throttle:search)
Meilisearch via ArtworkSearchService::search or popular(). Additional SQL NewsArticle LIKE '%q%' on title/excerpt/content/meta_title. Groups via GroupDiscoveryService. Canonical query 301. page_robots = noindex,follow.
Download GET /download/artwork/{id} (throttle:downloads)
Resolves original via ArtworkOriginalFileLocator; writes artwork_downloads row; increments counters (possibly twice — incrementDownloadCountIfAvailable + ArtworkStatsService::incrementDownloads(..., defer: false)). X-Accel-Redirect only if app.download_accel_enabled.
View write POST /api/art/{id}/view (throttle:120,1)
Inserts artwork_view_events and incrementViews(..., defer: false) (immediate MySQL, not Redis delta). XP award may fire.
8. Database Architecture
Engine: MySQL (DB_CONNECTION=mysql in .env.example). Laravel also defines sqlite/pgsql. Tests use sqlite :memory:.
Complexity: High. 308 migrations. Schema::create appears across 90+ migration files; unique table count is well over 100 (artworks, users, academy, collections, groups, worlds, forum, feed, rec, nova cards, ControlPanel, etc.). Exact production table count requires information_schema.
High-traffic / large-growth tables (from code + slow-query doc)
| Table | Purpose | Important columns | Indexes (from migrations) | Likely usage |
|---|---|---|---|---|
artworks |
primary content | user_id, slug, is_public, is_approved, published_at, deleted_at, trending_score_, visibility, maturity_ | PK; slug; browse (is_public,is_approved,published_at); batch1 (deleted_at,is_public,is_approved,published_at,id) and (deleted_at,is_public,is_approved,user_id); FULLTEXT (title,description) |
catalog, sitemaps, ranking jobs |
artwork_stats |
denormalized counters | views, downloads, favorites, windowed cols added later | PK artwork_id | joins on list/detail |
artwork_metric_snapshots_hourly |
hourly metrics | artwork_id, bucket_hour, counts | unique (artwork_id,bucket_hour); idx_bucket_hour; batch1 idx_bucket_artwork (bucket_hour,artwork_id) | rising/heat jobs |
artwork_view_events |
per-view log | artwork_id, user_id, viewed_at | (inspect later) | insert on every view; prune 90d |
artwork_downloads |
download log | artwork_id, user_id, ip | insert on download | |
artwork_favourites / likes / comments / shares / reactions |
engagement | FKs + timestamps | various | counts, rec jobs |
artwork_tag / artwork_category |
pivots | filters, sitemaps | ||
tags |
taxonomy | usage_count, artworks_count (batch1) | idx_tags_artworks_count | popular tags |
users / user_profiles / user_statistics |
accounts | profiles, toolbars | ||
rank_artwork_scores |
ranking | model_version, score_* | batch1 idx_mv_trending/new_hot/best | explore |
sessions |
session store | logged-in + leftover cookies | ||
cache / cache_locks |
default cache | if CACHE_STORE=database | ||
jobs / failed_jobs |
queues if database driver | payload | LIKE payload scans historically | |
rec_item_pairs / rec_artwork_recs |
recommenders | nightly jobs | ||
user_discovery_events |
personalization | queued ingest | ||
| ControlPanel tables | /cp |
admin |
Legacy connection projekti_old_skinbase is configured for import, not request path.
9. Existing Indexes
Artworks (core):
- PK
id user_idslug(unique dropped later; non-unique index remains)is_public,is_approved,published_atsingle-column- compound
idx_artworks_browse (is_public, is_approved, published_at) - compound
idx_public_approved_published_id (deleted_at, is_public, is_approved, published_at, id)— added 2026-04-26 batch1 - compound
idx_public_approved_user_id - FULLTEXT
artworks_title_description_fulltext(2026-04-26) - many maturity/feature/group indexes
Snapshots: unique (artwork_id, bucket_hour); idx_bucket_hour; idx_artwork_bucket; batch1 idx_bucket_artwork (bucket_hour, artwork_id).
Rank scores: (model_version, score_trending|new_hot|best) batch1.
Tags: artworks_count column + index (batch1) to replace correlated count(*) subquery.
Index vs query mismatches (EXPLAIN candidates — do not add yet)
- Related artworks on detail (
ArtworkPageController~157–184):WHERE user_id = ? OR group_id = ? OR whereHas categories OR whereHas tags+ORDER BY published_at. Existing indexes do not match this OR/whereHas pattern. Candidate requiring EXPLAIN. - Public count (
count(*)on public+approved+published): covering index exists after batch1; still a counter-cache candidate. - News search LIKE '%q%' on
content: no FULLTEXT observed on news in this pass. EXPLAIN + FULLTEXT/Meili candidate. - Profile username
whereRaw('LOWER(username) = ?'): functional lookup may skip normal unique index. EXPLAIN candidate. - Toolbar subqueries counting artworks/favourites/notifications per user (cached 30s): not a missing index issue if
user_idindexed; still correlated. - Historical Q1/Q2 aggregate listing joining derived fav/comment/share counts: batch1 index helps outer scan; inline derived joins still need EXPLAIN on current ranking commands.
- Jobs payload LIKE (historical AutoTag dedupe): no index possible; verify code no longer scans
jobs.payload.
10. Query Findings
QF-001 — Artwork detail related orWhereHas
File: app/Http/Controllers/Web/ArtworkPageController.php
Line/range: ~157–184
Function: show()
Query behavior: related artworks via OR of user_id, group_id, whereHas(categories), whereHas(tags), latest published_at, limit 12
Why expensive: whereHas exists-subqueries + OR prevents single index
Evidence: source
Confidence: high
QF-002 — Comments unbounded-ish load
File: ArtworkPageController.php
Line/range: ~209–215
Query behavior: ArtworkComment where artwork_id, is_approved, order created_at, limit 500
Why expensive: hydrates up to 500 comment+user.profile rows on every detail view
Evidence: source
Confidence: high
QF-003 — Search news leading-wildcard LIKE
File: app/Http/Controllers/Web/SearchController.php
Line/range: ~66–78
Query behavior: NewsArticle title/excerpt/content/meta_title LIKE '%q%'
Why expensive: cannot use B-tree; content column scan
Evidence: source; public /search is throttled but anonymous
Confidence: high
QF-004 — Meilisearch visibility fallback over-fetch
File: app/Services/ArtworkSearchService.php
Line/range: search() ~85–106; searchWithThumbnailPreference ~109–128
Query behavior: paginate candidate pool up to 240 then filter in PHP
Why expensive: large Meili page + extra round trip when maturity filtering required
Evidence: SEARCH_CANDIDATE_POOL_MAX = 240
Confidence: medium
QF-005 — ArtworkResource extra queries on detail serialize
File: app/Http/Resources/ArtworkResource.php
Line/range: ~23–80+
Query behavior: loadMissing relations; Schema::hasTable; user_statistics lookup; exists() on likes/bookmarks/favourites
Why expensive: several extra queries per detail; Schema::hasTable on hot path
Evidence: source
Confidence: high for detail; would be N+1 if used in lists (list uses ArtworkListResource)
QF-006 — Guest homepage aggregation (mitigated)
File: app/Services/HomepageService.php
Function: buildGuestPayload / all
Query behavior: many section queries
Why expensive on miss: large fan-out
Evidence: flexible cache + homepage:warm-guest-cache schedule
Confidence: high that miss is expensive; guest hit is mitigated
QF-007 — Authenticated homepage uncached
File: HomepageService::allForUser
Why expensive: repeats public rails + user_data counts + for_you + following
Evidence: no Cache::remember wrapping allForUser (only some subcalls)
Confidence: high
QF-008 — Toolbar correlated counts
File: app/Providers/AppServiceProvider.php View::composer layouts.nova
Line/range: ~226–258
Query behavior: selectSub COUNT artworks, favourites, unread notifications + messages join
Cached: Cache::remember toolbar:{userId} TTL config toolbar.cache_ttl_seconds default 30
Confidence: high
QF-009 — Historical production slow log (server3)
docs/slow-query-optimization-plan.md: 68,950 slow queries / 22 days / 15.39B rows examined. Top class: artwork aggregate SELECT with derived joins (~117,834s, 78% of slow time). Batch1 indexes and tag artworks_count were added in repo the same day as that analysis. Production re-measure required to know residual cost.
QF-010 — inRandomOrder()
Interview/user controllers (InterviewController, UserController). Low traffic vs gallery. Confidence: medium, likely P3.
QF-011 — Ranking/heat scheduled scans
Commands nova:recalculate-rankings, nova:metrics-snapshot-hourly, nova:recalculate-heat, skinbase:recalculate-trending every 15–30 minutes. Historical Q1/Q2 look like this class. Confidence: high they are heavy; medium they still match old SQL.
11. N+1 Findings
| Source | Relation | Amplification | Eager load? |
|---|---|---|---|
Gallery mapping using $a->user?->profile after loadMissing(['user.profile','categories.contentType']) |
user/profile/categories | none if loadMissing ran | present on search/tag/discover |
ArtworkListResource $user?->profile?->avatar_url |
profile | 1 per card if profile not loaded | list resource checks relationLoaded('user') but may still lazy-load profile |
ArtworkResource contributors.user.profile |
nested | per contributor | loadMissing on detail only |
Comments loop uses $c->user |
user.profile | 500 max | with('user.profile') present |
404 ErrorSuggestionService trending artworks/tags/creators |
various | extra queries on 404 | unknown eager |
| ControlPanel / DataTables | unknown | /cp 510 routes |
not fully traced |
| Nova toolbar content types | ContentTypeSlugResolver | every Blade nova layout | cached-ish via resolver |
No smoking-gun foreach ($items as $item) { $item->user } without eager load was confirmed on the main gallery path after loadMissing. Residual N+1 risk is profile avatar accessors and ControlPanel. Confidence: medium.
12. Cache Architecture
| Item | Value |
|---|---|
| Default store | env('CACHE_STORE', 'database') — database in .env.example |
| Redis store | defined; homepage failover redis, database, array |
| File store | storage/framework/cache/data |
| Tags | not a first-class pattern in app Cache:: calls reviewed |
| Key examples | homepage.payload.guest, homepage.tags.{n}, search.popular.{segment}.page.{n}, toolbar:{userId}, explore.cache.version, search.related.{id}.{segment} |
| TTL | 120–1800s typical; toolbar 30s; search 300s |
| Invalidation | ArtworkObserver increments explore.cache.version; homepage clear methods; some forget() on world stories |
| Production Redis for default cache | UNKNOWN |
Potential cache candidates (not implemented as a change here):
- Authenticated homepage modules
- Artwork detail related + comment trees
- Public artwork count
- Tag directory stats
- Error-page suggestion rails
- Ranking top-N lists (partially via RankBuildListsJob)
- Category directory
/searchempty-state popular (already remembered)
13. Session Architecture
| Item | Value |
|---|---|
| Driver | database (.env.example / config/session.php) |
| Table | sessions |
| Lifetime | 120 minutes |
| Lottery | 2/100 sweeps |
| Conditional sessions | config/skinbase-sessions.php enabled; skip anonymous public GET; skip bots |
| Always-session paths | login, dashboard, studio, upload, admin, messages, etc. |
| Public skip list | /, featured, discover, explore, art, tags, sitemaps, news, … |
If a session cookie already exists, session is not skipped even on public GET. That limits anonymous CDN/full-page cache for returning users.
HandleInertiaRequests avoids reading auth when session skipped.
Impact: full-page HTTP cache for anonymous first-time crawlers is feasible; cookie holders still hit origin.
14. Queue Architecture
| Item | Value |
|---|---|
| Default connection | .env.example redis; config fallback database |
| Horizon | name skinbase-nova; path /horizon; prefix skinbase_nova_horizon: |
| Horizon supervisors | search,default (timeout 960s, production maxProcesses 5); broadcasts,notifications (timeout 90s, maxProcesses 3) |
| Supervisor example | --timeout=90 queues search,forum-security,forum-moderation,vision,recommendations,discovery,mail,default numprocs=1 |
| systemd example | same --timeout=90 |
| Job timeout example | RecComputeSimilarHybridJob $timeout = 900, $tries = 1 |
CONFIRMED mismatch: deploy worker timeout 90s vs recommendation jobs 900s vs Horizon 960s. If production uses supervisor/systemd snippet rather than Horizon, 900s jobs will be killed.
Horizon does not list vision, recommendations, discovery, mail, forum-* as dedicated supervisors; those names only appear on the supervisor example. If Horizon is the production worker, those queues must be consumed via default or they stall. docs/QUEUE.md already warns Scout search queue must be consumed.
Job classes include: AutoTag, embeddings/vectors, derivatives, featured thumbs, IncrementArtworkView (queued variant exists but view controller uses sync increment), indexing, discovery ingest, rec compute, sitemaps, enhance, security report, nova cards, leaderboards, etc.
Failed jobs: Laravel failed_jobs table; Horizon trim failed 10080 minutes.
15. Scheduler
Defined in routes/console.php (Laravel 11+ style). app/Console/Kernel.php registers commands; schedules live in console.php.
| Task | Frequency | Purpose | Potential cost |
|---|---|---|---|
skinbase:recalculate-trending 24h |
:06/:36 | trending scores | HIGH (historical slow SQL class) |
| trending 7d | :19/:49 | HIGH | |
| reset-windowed-stats | daily/weekly 03:30/03:50 | counters | MEDIUM–HIGH |
| uploads/enhance cleanup | daily 03:00–03:30 | files | MEDIUM |
| analytics aggregates | 03:10–03:35 | discovery/feed/tags | MEDIUM |
| academy analytics | hourly + daily | MEDIUM | |
skinbase:flush-redis-stats |
every 10 min | drain deltas | LOW–MED (if views still sync, less used) |
| prune view events | Sunday 04:00 | delete old events | HIGH I/O if table large |
| rec pair/tag/behavior/hybrid jobs | 4h / 02:00–02:30 | similarity | HIGH (900s timeouts) |
| publish-scheduled posts/artworks/news/cards | every minute | publish | LOW–MED |
| collection lifecycle | every 10 min | MED | |
| homepage:warm-guest-cache | every 10 min offset | warm cache | MED on miss |
| artworks:search-reconcile | hourly :28 | Meili drift | MED |
| posts:warm-trending | every 2 min | LOW–MED | |
| nova:recalculate-rankings | :07/:37 | ranking v2 | HIGH |
| metrics-snapshot-hourly | :02 | snapshots | HIGH write |
| nova:recalculate-heat | every 15 min | heat | HIGH |
| sitemaps generate | 10:30 and 22:30 | XML | HIGH |
| sitemaps publish | every 6h :08 | MED | |
| sitemaps validate | 04:45 | MED | |
| security:scan | weekly Mon 05:15 | MED | |
| leaderboards / rank lists / nova card caches | hourly | MED | |
| prune metric snapshots keep 7d | daily 04:00 | HIGH I/O | |
| forum AI/bot/post/firewall scans | hourly | MED | |
| health:tick | every minute | LOW | |
| horizon:snapshot | hourly :45 | LOW |
Night window 02:00–05:00 is densely packed.
16. Search Architecture
/search?q=
→ throttle:search (20/user/min, 30/IP/min)
→ canonical 301
→ Artwork::search() Scout/Meilisearch
→ optional PHP visibility filter / candidate pool
→ NewsArticle LIKE '%q%'
→ GroupDiscoveryService
→ Blade search.index, robots noindex,follow
- Driver default meilisearch. Queue:
searchon Redis. - Indexes: artworks (rich filterable/sortable attrs), messages, plus Searchable on User, Group, Post.
- Autocomplete: React
SearchBar.jsx/ overlay (separate API; not fully traced). - Image/vector search:
/api/art/{id}/similar-ai,/api/search/imagewiththrottle:vector-search; nginx snippetsearch-rate-limit.conf. - Fallback SQL LIKE remains for news and historical artwork LIKE (slow-query doc); Scout path is primary for artworks.
- Scale constraint: Meilisearch cluster size UNKNOWN; PHP LIKE on news
contentwill not scale. - Search is noindex (good) but
robots.txtstillAllow: /so crawlers may hit it until they honor robots meta.
17. Statistics / Counters
| Signal | Mechanism | Sync? | Location |
|---|---|---|---|
| Artwork views | insert artwork_view_events + increment artwork_stats views/24h/7d |
sync, defer:false | ArtworkViewController, ArtworkStatsService |
| Queued IncrementArtworkView | exists | not used by this controller | app/Jobs/IncrementArtworkView.php |
| Redis deltas | artwork_stats:deltas + skinbase:flush-redis-stats |
unused when defer=false | ArtworkStatsService |
| Downloads | row in artwork_downloads + increment (possibly duplicated) |
sync | ArtworkDownloadController |
| Favourites/likes/comments | tables + observers | write path | observers/jobs medal stats |
| Ranking/heat | scheduled recompute | async | commands |
| XP | XPService on view | sync on view POST | |
| Presence | Redis | after response | TrackOnlineVisitor |
| Profile views | not confirmed as a dedicated counter |
High-write path (P0/P1): every public artwork view POST = at least one INSERT + one UPDATE, throttle 120/min/IP, CSRF excepted for api/art/*/view.
18. Media Pipeline
| Item | Actual |
|---|---|
| Upload max | 50 MB image, 200 MB archive (config/uploads.php) |
| MIME | jpeg/png/webp; GIF off by default |
| Derivatives | xs 320, sm 680, md 1024, lg 1920, xl 2560, sq 512 |
| Featured variants | mobile_xs 400 … desktop_xl 2200×1238 |
| Quality | 85 default; sq 82 |
| Queue derivatives | UPLOAD_QUEUE_DERIVATIVES default false → synchronous publish pipeline unless enabled |
| GenerateDerivativesJob | exists; used when queued |
| Presenter widths | xs 160 / sm 320 / md 640 / lg 1280 / xl 1920 / sq 400 — mismatch vs derivative max sizes |
| Format | WebP thumbs typical; originals jpeg/png/webp |
| AVIF | not a first-class upload MIME |
| CDN | https://cdn.skinbase.org; missing thumbs https://files.skinbase.org/default |
| srcset | ThumbnailPresenter builds srcset; Blade galleries pass thumb_srcset; homepage hero preload + fetchpriority high |
| lazy | widespread loading="lazy"; homepage hero is preloaded (good LCP) |
| Lighthouse (skinbase.top, 2026-03-23) | LCP 0.8s, FCP 0.7s, CLS 0.017, TBT 0ms, performance 0.99 — one historical homepage lab run, not production skinbase.org proof |
19. Filesystem / CDN
Disks (config/filesystems.php): local (private), public, sitemaps_public (public_path), s3.
Artwork roots (config/uploads.php):
storage/app/artworks- local originals
storage/app/originals/artworks - readonly backup
/opt/www/virtual/files/cdn/artworks/original - object disk
s3prefixartworks
Download may stream through PHP if accel disabled (deploy/nginx/download-accel.conf documents FPM buffering problem).
CDN purge webhook optional (CDN_PURGE_URL, Cloudflare zone/token).
20. Frontend Architecture
Hybrid:
- Blade Nova MPA for homepage, discover, explore, search, many public galleries.
- Inertia + React 19 for studio, admin, profile, upload, feed, forum, academy pages, collections manage.
- SSR enabled (
config/inertia.phpssr.enabled=true, urlhttp://127.0.0.1:13714);deploy/supervisor/skinbase-ssr.confrunsbootstrap/ssr/ssr.js. Homepage excluded from SSR graph (resources/js/ssr.jsx). - Vite 7, Tailwind 3 (+ vite v4 plugin present), Sass, Alpine in devDependencies.
- Notable JS: TipTap, highlight.js/lowlight, emoji-mart + data, framer-motion, Echo/pusher-js, marked, react-markdown, turndown.
21. Frontend Payload Findings
| Candidate | Evidence |
|---|---|
Guest homepage JSON-ish props |
HomepageService serializes many rails (10 items × many sections + collections + tags + creators + news). Cached. |
| Authenticated homepage | same plus for_you / following / user_data — uncached |
| Artwork detail | ArtworkResource large graph + related 12 + comments ≤500 |
| Profile page | artworks page + featured + favourites + stats in one Inertia render |
| Shared Inertia | auth user flags + studio_groups via GroupService for every Inertia page when logged in (HandleInertiaRequests::share) |
| Search | full paginator + news + groups |
| TipTap / emoji-data chunks | 481 KB + 423 KB — studio/editor, not homepage |
ArtworkListResource is relatively slim (good). Detail resource is fat (expected).
22. Build Assets
Existing public/build inspected (build not re-run, to avoid dirtying tree).
| Metric | Value |
|---|---|
| JS files | 260, 5.73 MB total |
| CSS files | 5, 500 KB total |
| Largest JS | vendor-tiptap 481 KB; emoji-data 423 KB; vendor-syntax 307 KB |
| Largest CSS | app-oMtr0NO-.css 429 KB |
| Manual chunks | tiptap, syntax, tooltip, motion, realtime, emoji (vite.config.mjs) |
npm run build was not executed (node_modules present; existing hashed assets sufficient for size evidence).
23. SEO
Shared system: config/seo.php, SeoFactory / SeoDataBuilder, Blade partials/seo/head.blade.php, Inertia SeoHead.jsx (docs/seo-audit-system-v1-summary.md).
| Page type | Title | Description | Canonical | Indexability | Structured data | Potential issue |
|---|---|---|---|---|---|---|
| Homepage | Skinbase – Digital Art & Wallpapers | yes | url('/') |
index,follow | WebSite + SearchAction | /home duplicate |
| Artwork | {title} by {author} — Skinbase |
stripped desc | route('art.show') 301 slug |
index | ImageObject + CreativeWork | second URL via content-type path |
| Discover | page_title set | yes | canonicalRoute | index | via unified seo if wired | sort/pagination params |
| Search | Search: q | yes | canonicalized query | noindex,follow | still Allow:/ in robots | |
| Tags | Browse Tags | yes | tags.index | index | two /tags/{tag} route patterns |
|
| Profile | via SeoFactory | lowercase username 301 | index | tab= redirected to path |
||
| 404 | errors layout | noindex,nofollow | extra suggestion queries |
OG/Twitter: summary_large_image; fallback /gfx/skinbase_back_001.webp.
24. Canonicals / Duplicate URLs
Flagged surfaces (no redirects implemented in this audit):
| Surface | Evidence |
|---|---|
/ and /home |
both HomeController |
/art/{id}/{slug} vs /{type}/{category}/{artwork} |
both public artwork URLs |
/explore/top-rated 301 to explore?sort= |
good |
/discover 301 to /discover/trending |
good |
| Search extra params 301 stripped | good |
| Profile case / username_redirects | 301 |
/contact defined twice |
PageController marketing and ApplicationController contact.show (later wins in Laravel) |
/tags/{tag} vs feed hashtag regex |
possible overlap |
| Explore sort querystrings | multiple indexable sorts unless canonicalized |
Legacy gallery.php, /lost-password |
301 |
| www/HTTPS | not in app; server UNKNOWN |
public/robots.txt vs dynamic RobotsTxtController |
both exist; route GET /robots.txt uses controller |
25. Sitemaps
- Routes:
/sitemap.xml,/sitemaps/{name}.xml - Config: shard size 10k; many families (artworks, users, tags, academy-*, news-google, forum, …)
- Scheduler: generate 10:30/22:30, publish every 6h, validate daily, cleanup job
- nginx snippet serves static files first
- Defaults:
SITEMAPS_BUILD_ON_REQUEST=true,SITEMAPS_FALLBACK_TO_LIVE_BUILD=true— if static/published missing, PHP builds sitemap on crawler request public/sitemap.xmlis deleted in dirty tree; static files underpublic/sitemaps/exist- Cache-Control on PHP/file responses uses
sitemaps.cache_ttl_secondsdefault 900; nginx snippet 6h
Potentially expensive: live-build of sharded artworks sitemap on cache miss.
26. robots.txt
Controller output:
User-agent: *
Allow: /
Sitemap: {APP_URL}/sitemap.xml
Static public/robots.txt matches (sitemap hard-coded https://skinbase.org/sitemap.xml).
- No
Disallowfor/search,/explore?*,/cp,/studio,/horizon, APIs - Relies on meta robots / auth for private areas
- Parameter crawl explosion: POSSIBLE for sort/filter/pagination
- Search is noindex in HTML (good) but still allowed
27. Web Server / HTTP
Repo provides snippets only, not a full production vhost:
deploy/nginx/static-cache.conf— 1y hashed assets; 7d/imagessitemaps.conf— try_files then PHPdownload-accel.conf— internal X-Accelsearch-rate-limit.conf— untracked in dirty tree; 20r/m search, 10r/m AI searchupstream-error-pages.conf— 502/504 static HTML
Production web-server configuration not available in repository (no live nginx.conf, TLS, HTTP/2/3, Brotli, gzip, FPM socket, client_max_body_size).
App security headers middleware: X-Frame-Options SAMEORIGIN, nosniff, Referrer-Policy, Permissions-Policy. CSP not set here.
28. PHP Runtime
| Item | Repo evidence |
|---|---|
| Version | ^8.2; platform 8.4 extensions |
| OPcache / memory_limit / FPM | not in repo |
| CLI | 124 commands; Horizon memory 128MB workers; master 64MB |
| Upload | app 50MB; PHP upload_max_filesize UNKNOWN |
29. Dependencies
Performance-relevant: Horizon, Scout/Meilisearch, Redis/predis, Intervention Image, Flysystem S3, Reverb.
Image: intervention/image, gumlet/php-image-resize.
Debug: fruitcake/laravel-debugbar (require-dev). bootstrap/cache/packages.php currently lists debugbar — this local environment has it discovered. Production composer install --no-dev (docs/deployment.md) should omit it. var/routes.json includes _debugbar routes.
composer audit (read-only): advisories reported for transitive guzzlehttp/guzzle (9), guzzlehttp/psr7 (2), league/commonmark (6), mtdowling/jmespath.php (1), phpseclib/phpseclib (1). Abandoned: none. Do not treat this as a full CVE triage; versions/severity need human review. No secrets printed.
npm audit: not completed in this pass.
30. Security / Abuse-Sensitive Paths
Not a pentest. Performance-adjacent:
| Path | Control | Residual |
|---|---|---|
/search |
throttle 20/30 per min; query param cap 15 / 500 chars | still Meili+SQL LIKE |
/api/art/{id}/view |
120/min; CSRF excepted | cheap to write DB |
/api/art/{id}/similar-ai |
vector-search limiter; nginx snippet | outbound vision HTTP |
| Downloads | 60/120 per min | originals via PHP if no accel |
| Uploads | per-user/IP chunk limits; draft quotas | sync image work if queue flag false |
| Forum | dedicated firewall/bot/AI middleware | scheduled scans |
| Registration | Turnstile optional, disposable domains, rate limits | |
| Horizon | web middleware only in config — auth gating UNKNOWN |
|
/cp |
ControlPanel package | large attack/admin surface |
| Stripe webhooks | CSRF except stripe/* |
31. Error Handling
bootstrap/app.php: Sentry; 404 → ErrorController::handleNotFound (pattern-specific suggestion queries); ModelNotFound same; 403/other HTTP → Blade errors.{status} or errors.http; 500 logs correlation id via NotFoundLogger unless APP_DEBUG.
404 pages run extra DB (trending artworks, tags, creators). Confidence: medium they are cached internally (not verified).
JSON/Inertia 404 returns minimal JSON.
32. Tests
Inventory: Pest Feature 227 + Unit 45 + Playwright e2e 11 + Vitest script.
php artisan test was started. Partial results before log truncation:
- Many unit tests PASS (academy, collections, discovery, early growth, enhance, sanitizer, …)
- FAIL observed:
Tests\Unit\ForumRateLimitRouteTestTests\Unit\ForumRestrictedCategoryAccessTest(2)Tests\Unit\HomepageAnnouncementModuleTest(2: homepage payload/render)
Full suite did not finish in the captured log (output truncated; duration incomplete). Tests were not modified to pass.
Playwright / Vitest / npm run build not re-run.
33. Existing Performance Tooling
| Artifact | What it is |
|---|---|
docs/slow-query-optimization-plan.md |
Production slow.log analysis + index plan |
database/migrations/2026_04_26_082019_add_performance_indexes_batch1.php |
Indexes from that plan |
lighthouse_metrics.json |
Lighthouse 13 lab run vs https://skinbase.top/ 2026-03-23 |
scripts/parse-lighthouse.js, parse-lcp.cjs |
parsers |
Debugbar storage storage/debugbar/*.json |
local query traces |
| Horizon metrics snapshots | scheduled |
| Playwright e2e | functional, not load |
| No k6/ab/wrk/Artillery found |
34. Optimization Opportunity Matrix
| ID | Area | Finding | Evidence | Impact | Confidence | Priority | Production measurement needed |
|---|---|---|---|---|---|---|---|
| DB-001 | DB | Historical 78% slow time on artwork aggregate listing/joins | slow-query doc Q1/Q2 | very high | high historical; residual UNKNOWN | P0 | EXPLAIN current ranking SQL; table sizes |
| DB-002 | DB | Detail related orWhereHas OR query |
ArtworkPageController | high TTFB on detail | high | P1 | EXPLAIN + p95 art.show |
| DB-003 | DB | Comments load 500 | ArtworkPageController | payload/TTFB | high | P1 | p95 comment counts |
| DB-004 | DB | News LIKE %q% on search |
SearchController | search tail latency | high | P1 | slow log / EXPLAIN |
| DB-005 | DB | LOWER(username) profile lookup |
ProfileController | profile TTFB | medium | P2 | EXPLAIN |
| CACHE-001 | Cache | Default CACHE_STORE=database | .env.example / config/cache.php | global | medium (prod unknown) | P1 | prod CACHE_STORE, cache table size |
| CACHE-002 | Cache | Authenticated homepage uncached | HomepageService::allForUser | TTFB logged-in | high | P1 | logged-in homepage trace |
| QUEUE-001 | Queue | Worker timeout 90s vs jobs 900s | deploy supervisor/systemd vs RecComputeSimilar* | failed jobs / stuck recs | high | P0 | which worker actually runs in prod |
| QUEUE-002 | Queue | Horizon supervisors omit vision/discovery/mail names | horizon.php vs QUEUE.md | stalled AI jobs | medium | P1 | Horizon dashboard queues |
| STAT-001 | Stats | View POST writes event+counter sync | ArtworkViewController defer:false | write amplification | high | P0 | views/sec, innodb row ops |
| STAT-002 | Stats | Download may double-increment | ArtworkDownloadController | incorrect + extra writes | medium | P1 | code path test + QPS |
| SITEMAP-001 | SEO/Infra | Live-build fallback on request | config/sitemaps.php defaults | crawler-triggered heavy PHP | high | P0 | whether static files always present |
| SEO-001 | SEO | robots Allow:/ no Disallow search/filters | RobotsTxtController | crawl budget | medium | P1 | GSC crawl stats |
| SEO-002 | SEO | Duplicate artwork URLs | art.show vs browse showArtwork | ranking dilution | high | P1 | GSC duplicates |
| MEDIA-001 | Media | Derivatives sync by default | UPLOAD_QUEUE_DERIVATIVES=false | upload latency / FPM | high | P1 | upload p95 |
| MEDIA-002 | Media | Presenter sizes ≠ derivative sizes | ThumbnailPresenter vs uploads.php | wrong srcset / overfetch | high | P2 | RUM image bytes |
| MEDIA-003 | Media | PHP download without accel | download-accel + controller | FPM blocked on large files | high if accel off | P1 | confirm DOWNLOAD_ACCEL |
| SEARCH-001 | Search | Meili candidate pool 240 | ArtworkSearchService | extra Meili/PHP work | medium | P2 | Meili latency |
| SEARCH-002 | Search | nginx search limiter is snippet/untracked | search-rate-limit.conf | FPM flood | medium | P1 | whether included in vhost |
| FE-001 | Frontend | Large CSS 429KB + editor chunks | public/build | studio CWV not homepage | high | P2 | page-type coverage |
| FE-002 | Frontend | Inertia share studio_groups | HandleInertiaRequests | extra query logged-in | medium | P2 | Inertia traces |
| INFRA-001 | Infra | Production nginx/FPM/OPcache unknown | deploy snippets only | — | — | P0 data | collect server config |
| SEC-001 | Security | composer audit advisories | composer audit | supply chain | medium | P2 | version pin review |
| SEC-002 | Security | Debugbar discovered locally; _debugbar routes in dump | packages.php, routes.json | must not be in prod | medium | P1 | prod composer --no-dev |
| ERR-001 | Errors | 404 runs suggestion queries | ErrorController | bot 404 load | medium | P2 | 404 QPS |
35. P0 Findings
- QUEUE-001 — Deployed example workers use
--timeout=90while recommendation jobs declare$timeout = 900. Horizon allows 960s. CONFIRMED in repo; production worker type UNKNOWN. - STAT-001 — Artwork views persist an event row and increment MySQL counters on the request (
defer: false), CSRF-excepted, 120/min. CONFIRMED. - SITEMAP-001 — Config defaults rebuild sitemaps in-request if published/static missing. Dirty tree deleted
public/sitemap.xml. CONFIRMED default; production file presence UNKNOWN. - DB-001 — Production slow log (Apr 2026) showed artwork aggregate scans as ~78% of slow time. Batch1 indexes exist in migrations; residual production cost UNKNOWN / requires re-EXPLAIN. Treated P0 until measured clear.
36. P1 Findings
CACHE-001, CACHE-002, QUEUE-002, STAT-002, SEO-001, SEO-002, MEDIA-001, MEDIA-003, SEARCH-002, SEC-002, DB-002, DB-003, DB-004, plus:
- TrackOnlineVisitor Redis write after almost every public GET.
- Toolbar correlated counts every 30s per user on Nova layouts.
- Historical jobs-table LIKE dedupe pattern (verify gone).
- Download original serving vs CDN derivatives.
37. P2 Findings
DB-005, MEDIA-002, SEARCH-001, FE-001, FE-002, ERR-001, SEC-001, inRandomOrder interviews, comment/N+1 residuals, presenter vs files.skinbase.org fallback host mismatch vs cdn.skinbase.org, duplicate /contact routes, packed 02:00–05:00 scheduler, academy/forum hourly jobs, Lighthouse only for skinbase.top.
38. P3 Findings
- gumlet/php-image-resize alongside Intervention.
- Tailwind v3 + v4 vite plugin coexistence.
- Default Laravel README still in repo.
oldSite/large binary tree in repository.- Debugbar JSON in
storage/debugbar(local clutter). - yajra/datatables unbounded version
*. - jenssegers/agent
*. - ControlPanel menu composer cost not profiled.
39. Risky Areas
| Area | Why sensitive |
|---|---|
| Ranking / heat / trending SQL | historically the slow-log majority; changing formulas affects homepage/discover |
| View/download counters | product metrics, medals, trending inputs |
| Upload/derivative pipeline | data loss / hash-addressed CDN |
| Meilisearch index settings | search relevance; Scout observer disabled on Artwork by design |
| Billing / Cashier / Academy | money |
| Maturity / moderation | legal/safety |
| Conditional sessions | auth bugs if skip logic wrong |
| Sitemap publish | SEO indexing |
ControlPanel /cp |
admin |
| Vector/CLIP/YOLO gateways | latency and cost |
| Legacy routes + oldSite | accidental dual-write / dead links |
40. Production Data Required
Collect values only — never passwords, tokens, keys.
- OS, CPU, RAM, disk type/latency
- nginx version + full vhost (gzip/brotli, HTTP/2/3, body size, includes of deploy snippets)
- PHP-FPM version, pool (
pm, max_children),memory_limit,max_execution_time,upload_max_filesize,post_max_size, OPcache - MySQL version, buffer pool, connections, slow_query_log threshold, table sizes/row estimates
- Redis version, memory, eviction, Horizon vs supervisor actually running, queue depths, failed jobs
- Meilisearch host, index sizes, RAM
- CDN provider, cache hit ratio, bandwidth
- Production
.envnon-secret drivers:CACHE_STORE,SESSION_DRIVER,QUEUE_CONNECTION,SCOUT_DRIVER,FILESYSTEM_DISK,UPLOAD_QUEUE_DERIVATIVES,DOWNLOAD_ACCEL_*,SITEMAPS_*,APP_DEBUG - Request QPS, peak, top URLs, 404 rate
- Whether
composer install --no-devis used - Whether debugbar/horizon are publicly reachable
- Confirm skinbase.org vs skinbase.top relationship
41. Recommended Production SQL Diagnostics
Read-only. Prefer information_schema estimates over COUNT(*) on huge tables.
-- Database size
SELECT table_schema,
ROUND(SUM(data_length+index_length)/1024/1024,1) AS mb
FROM information_schema.tables
WHERE table_schema = DATABASE();
-- Table sizes (no full counts)
SELECT table_name,
table_rows,
ROUND(data_length/1024/1024,1) AS data_mb,
ROUND(index_length/1024/1024,1) AS index_mb
FROM information_schema.tables
WHERE table_schema = DATABASE()
ORDER BY data_length+index_length DESC
LIMIT 40;
SHOW INDEX FROM artworks;
SHOW INDEX FROM artwork_metric_snapshots_hourly;
SHOW INDEX FROM rank_artwork_scores;
SHOW INDEX FROM tags;
SHOW GLOBAL STATUS LIKE 'Threads_connected';
SHOW GLOBAL STATUS LIKE 'Slow_queries';
SHOW VARIABLES LIKE 'slow_query%';
SHOW VARIABLES LIKE 'innodb_buffer_pool_size';
EXPLAIN candidates (use production-realistic binds, avoid long locks):
- Current
nova:recalculate-rankings/ trending SQL (compare to old Q1/Q2) - Artwork related
orWhereHaspattern - News LIKE search
LOWER(username)profile lookupartwork_view_eventsinsert rate vs table size
Do not SELECT * dump artwork_metric_snapshots_hourly.
42. Recommended Server Diagnostics
Read-only:
uptime; free -h; df -h
iostat -x 1 5
ps aux | egrep 'php-fpm|nginx|redis|meili|horizon|queue:work|ssr.js'
nginx -T | egrep 'gzip|brotli|http2|client_max_body|limit_req|include'
php -i | egrep 'opcache|memory_limit|max_execution|upload_max|post_max'
redis-cli INFO memory
php artisan horizon:status
php artisan queue:failed --json | head
43. Benchmark URL Set
Use templates; do not invent IDs.
GET /(anonymous, cold and warm)GET /(authenticated)GET /discover/trendingGET /discover/freshGET /exploreGET /explore/{type}/trending(e.g. wallpapers)GET /{contentType}/{category}page 1 and page 5GET /art/{id}/{slug}typicalGET /art/{id}/{slug}high-comment artworkGET /@{username}GET /search?q={common-term}GET /search(empty)GET /tags/{popular-tag}GET /download/artwork/{id}(auth and guest)POST /api/art/{id}/viewGET /sitemap.xmlGET /sitemaps/artworks-0001.xml(or first shard)GET /news/{slug}GET /categories- Unknown URL 404 (
/no-such-page-xyz)
44. Unknowns / Limitations
- Production
.envand live nginx/PHP/MySQL not inspected. - Dirty working tree means some findings (sitemaps, robots, search, academy) include uncommitted work.
- Full
php artisan testlog truncated; not a complete pass/fail census. npm audit/ production build not re-run.- ControlPanel internals not query-audited in depth.
- Subagent fan-out expired; this report is from direct inspection.
- Lighthouse file is skinbase.top, March 2026 — may not match skinbase.org today.
- Slow-query document predates or coincides with batch1 indexes; residual UNKNOWN.
45. Repository Changes Made
Created:
- docs/skinbase-optimization-audit.md
Modified:
- none (by this audit)
Application source changes:
- none
Database changes:
- none
Configuration changes:
- none
Pre-existing dirty files were left untouched.
Verify after write with git status --short / git diff --stat (audit file should be the only new path from this milestone).