CINXE.COM

MediaWiki Engineering/Guides/Backend performance practices - Wikitech

<!DOCTYPE html> <html class="client-nojs vector-feature-language-in-header-enabled vector-feature-language-in-main-page-header-disabled vector-feature-sticky-header-disabled vector-feature-page-tools-pinned-disabled vector-feature-toc-pinned-clientpref-1 vector-feature-main-menu-pinned-disabled vector-feature-limited-width-clientpref-1 vector-feature-limited-width-content-enabled vector-feature-custom-font-size-clientpref-1 vector-feature-appearance-pinned-clientpref-1 vector-feature-night-mode-disabled skin-theme-clientpref-day vector-toc-available" lang="en" dir="ltr"> <head> <meta charset="UTF-8"> <title>MediaWiki Engineering/Guides/Backend performance practices - Wikitech</title> <script>(function(){var className="client-js vector-feature-language-in-header-enabled vector-feature-language-in-main-page-header-disabled vector-feature-sticky-header-disabled vector-feature-page-tools-pinned-disabled vector-feature-toc-pinned-clientpref-1 vector-feature-main-menu-pinned-disabled vector-feature-limited-width-clientpref-1 vector-feature-limited-width-content-enabled vector-feature-custom-font-size-clientpref-1 vector-feature-appearance-pinned-clientpref-1 vector-feature-night-mode-disabled skin-theme-clientpref-day vector-toc-available";var cookie=document.cookie.match(/(?:^|; )labswikimwclientpreferences=([^;]+)/);if(cookie){cookie[1].split('%2C').forEach(function(pref){className=className.replace(new RegExp('(^| )'+pref.replace(/-clientpref-\w+$|[^\w-]+/g,'')+'-clientpref-\\w+( |$)'),'$1'+pref+'$2');});}document.documentElement.className=className;}());RLCONF={"wgBreakFrames":false,"wgSeparatorTransformTable":["",""],"wgDigitTransformTable":["",""],"wgDefaultDateFormat": "dmy","wgMonthNames":["","January","February","March","April","May","June","July","August","September","October","November","December"],"wgRequestId":"8487dfca-bf35-443b-b822-637b099ffd33","wgCanonicalNamespace":"","wgCanonicalSpecialPageName":false,"wgNamespaceNumber":0,"wgPageName":"MediaWiki_Engineering/Guides/Backend_performance_practices","wgTitle":"MediaWiki Engineering/Guides/Backend performance practices","wgCurRevisionId":2233113,"wgRevisionId":2233113,"wgArticleId":451802,"wgIsArticle":true,"wgIsRedirect":false,"wgAction":"view","wgUserName":null,"wgUserGroups":["*"],"wgCategories":["MediaWiki Engineering"],"wgPageViewLanguage":"en","wgPageContentLanguage":"en","wgPageContentModel":"wikitext","wgRelevantPageName":"MediaWiki_Engineering/Guides/Backend_performance_practices","wgRelevantArticleId":451802,"wgIsProbablyEditable":false,"wgRelevantPageIsProbablyEditable":false,"wgRestrictionEdit":[],"wgRestrictionMove":[],"wgRedirectedFrom": "Performance/Guides/Backend_performance_practices","wgNoticeProject":"wikitech","wgCiteReferencePreviewsActive":true,"wgMediaViewerOnClick":true,"wgMediaViewerEnabledByDefault":true,"wgVisualEditor":{"pageLanguageCode":"en","pageLanguageDir":"ltr","pageVariantFallbacks":"en"},"wgMFDisplayWikibaseDescriptions":{"search":true,"watchlist":true,"tagline":false,"nearby":true},"wgWMESchemaEditAttemptStepOversample":false,"wgWMEPageLength":60000,"wgInternalRedirectTargetUrl":"/wiki/MediaWiki_Engineering/Guides/Backend_performance_practices","wgCentralAuthMobileDomain":false,"wgEditSubmitButtonLabelPublish":true,"wgDiscussionToolsFeaturesEnabled":{"replytool":true,"newtopictool":true,"sourcemodetoolbar":true,"topicsubscription":false,"autotopicsub":false,"visualenhancements":false,"visualenhancements_reply":false,"visualenhancements_pageframe":false},"wgDiscussionToolsFallbackEditMode":"visual","wgULSPosition":"personal","wgULSisCompactLinksEnabled":false,"wgVector2022LanguageInHeader":true, "wgULSisLanguageSelectorEmpty":false,"wgCheckUserClientHintsHeadersJsApi":["brands","architecture","bitness","fullVersionList","mobile","model","platform","platformVersion"],"wgSiteNoticeId":"2.0"};RLSTATE={"ext.globalCssJs.user.styles":"ready","site.styles":"ready","user.styles":"ready","ext.globalCssJs.user":"ready","user":"ready","user.options":"loading","ext.cite.styles":"ready","ext.discussionTools.init.styles":"ready","oojs-ui-core.styles":"ready","oojs-ui.styles.indicators":"ready","mediawiki.widgets.styles":"ready","oojs-ui-core.icons":"ready","skins.vector.search.codex.styles":"ready","skins.vector.styles":"ready","skins.vector.icons":"ready","ext.wikimediamessages.styles":"ready","ext.visualEditor.desktopArticleTarget.noscript":"ready","ext.uls.pt":"ready","ext.dismissableSiteNotice.styles":"ready"};RLPAGEMODULES=["mediawiki.action.view.redirect","ext.cite.ux-enhancements","site","mediawiki.page.ready","mediawiki.toc","skins.vector.js","ext.centralNotice.geoIP", "ext.centralNotice.startUp","ext.gadget.site","ext.urlShortener.toolbar","ext.centralauth.centralautologin","ext.visualEditor.desktopArticleTarget.init","ext.visualEditor.targetLoader","ext.echo.centralauth","ext.discussionTools.init","ext.eventLogging","ext.wikimediaEvents","ext.uls.interface","ext.checkUser.clientHints","ext.dismissableSiteNotice"];</script> <script>(RLQ=window.RLQ||[]).push(function(){mw.loader.impl(function(){return["user.options@12s5i",function($,jQuery,require,module){mw.user.tokens.set({"patrolToken":"+\\","watchToken":"+\\","csrfToken":"+\\"}); }];});});</script> <link rel="stylesheet" href="/w/load.php?lang=en&amp;modules=ext.cite.styles%7Cext.discussionTools.init.styles%7Cext.dismissableSiteNotice.styles%7Cext.uls.pt%7Cext.visualEditor.desktopArticleTarget.noscript%7Cext.wikimediamessages.styles%7Cmediawiki.widgets.styles%7Coojs-ui-core.icons%2Cstyles%7Coojs-ui.styles.indicators%7Cskins.vector.icons%2Cstyles%7Cskins.vector.search.codex.styles&amp;only=styles&amp;skin=vector-2022"> <script async="" src="/w/load.php?lang=en&amp;modules=startup&amp;only=scripts&amp;raw=1&amp;skin=vector-2022"></script> <meta name="ResourceLoaderDynamicStyles" content=""> <link rel="stylesheet" href="/w/load.php?lang=en&amp;modules=site.styles&amp;only=styles&amp;skin=vector-2022"> <meta name="generator" content="MediaWiki 1.44.0-wmf.4"> <meta name="referrer" content="origin"> <meta name="referrer" content="origin-when-cross-origin"> <meta name="robots" content="max-image-preview:standard"> <meta name="format-detection" content="telephone=no"> <meta name="viewport" content="width=1120"> <meta property="og:title" content="MediaWiki Engineering/Guides/Backend performance practices - Wikitech"> <meta property="og:type" content="website"> <link rel="icon" href="/static/favicon/wikitech.ico"> <link rel="search" type="application/opensearchdescription+xml" href="/w/rest.php/v1/search" title="Wikitech (en)"> <link rel="EditURI" type="application/rsd+xml" href="//wikitech.wikimedia.org/w/api.php?action=rsd"> <link rel="canonical" href="https://wikitech.wikimedia.org/wiki/MediaWiki_Engineering/Guides/Backend_performance_practices"> <link rel="license" href="https://creativecommons.org/licenses/by-sa/4.0/"> <link rel="alternate" type="application/atom+xml" title="Wikitech Atom feed" href="/w/index.php?title=Special:RecentChanges&amp;feed=atom"> <link rel="dns-prefetch" href="//meta.wikimedia.org" /> <link rel="dns-prefetch" href="//login.wikimedia.org"> </head> <body class="ext-discussiontools-replytool-enabled ext-discussiontools-newtopictool-enabled ext-discussiontools-sourcemodetoolbar-enabled skin--responsive skin-vector skin-vector-search-vue mediawiki ltr sitedir-ltr mw-hide-empty-elt ns-0 ns-subject page-MediaWiki_Engineering_Guides_Backend_performance_practices rootpage-MediaWiki_Engineering skin-vector-2022 action-view"><a class="mw-jump-link" href="#bodyContent">Jump to content</a> <div class="vector-header-container"> <header class="vector-header mw-header"> <div class="vector-header-start"> <nav class="vector-main-menu-landmark" aria-label="Site"> <div id="vector-main-menu-dropdown" class="vector-dropdown vector-main-menu-dropdown vector-button-flush-left vector-button-flush-right" > <input type="checkbox" id="vector-main-menu-dropdown-checkbox" role="button" aria-haspopup="true" data-event-name="ui.dropdown-vector-main-menu-dropdown" class="vector-dropdown-checkbox " aria-label="Main menu" > <label id="vector-main-menu-dropdown-label" for="vector-main-menu-dropdown-checkbox" class="vector-dropdown-label cdx-button cdx-button--fake-button cdx-button--fake-button--enabled cdx-button--weight-quiet cdx-button--icon-only " aria-hidden="true" ><span class="vector-icon mw-ui-icon-menu mw-ui-icon-wikimedia-menu"></span> <span class="vector-dropdown-label-text">Main menu</span> </label> <div class="vector-dropdown-content"> <div id="vector-main-menu-unpinned-container" class="vector-unpinned-container"> <div id="vector-main-menu" class="vector-main-menu vector-pinnable-element"> <div class="vector-pinnable-header vector-main-menu-pinnable-header vector-pinnable-header-unpinned" data-feature-name="main-menu-pinned" data-pinnable-element-id="vector-main-menu" data-pinned-container-id="vector-main-menu-pinned-container" data-unpinned-container-id="vector-main-menu-unpinned-container" > <div class="vector-pinnable-header-label">Main menu</div> <button class="vector-pinnable-header-toggle-button vector-pinnable-header-pin-button" data-event-name="pinnable-header.vector-main-menu.pin">move to sidebar</button> <button class="vector-pinnable-header-toggle-button vector-pinnable-header-unpin-button" data-event-name="pinnable-header.vector-main-menu.unpin">hide</button> </div> <div id="p-navigation" class="vector-menu mw-portlet mw-portlet-navigation" > <div class="vector-menu-heading"> Navigation </div> <div class="vector-menu-content"> <ul class="vector-menu-content-list"> <li id="n-mainpage-description" class="mw-list-item"><a href="/wiki/Main_Page" title="Visit the main page [z]" accesskey="z"><span>Main page</span></a></li><li id="n-recentchanges" class="mw-list-item"><a href="/wiki/Special:RecentChanges" title="A list of recent changes in the wiki [r]" accesskey="r"><span>Recent changes</span></a></li><li id="n-Server-admin-log:-Prod" class="mw-list-item"><a href="/wiki/Server_Admin_Log"><span>Server admin log: Prod</span></a></li><li id="n-Admin-log:-RelEng" class="mw-list-item"><a href="/wiki/Release_Engineering/SAL"><span>Admin log: RelEng</span></a></li><li id="n-Incident-status" class="mw-list-item"><a href="/wiki/Incident_status"><span>Incident status</span></a></li><li id="n-Deployments" class="mw-list-item"><a href="/wiki/Deployments"><span>Deployments</span></a></li><li id="n-SRE-Team-Help" class="mw-list-item"><a href="/wiki/SRE/SRE_Team_requests"><span>SRE Team Help</span></a></li> </ul> </div> </div> <div id="p-Cloud_VPS_&amp;_Toolforge" class="vector-menu mw-portlet mw-portlet-Cloud_VPS_Toolforge" > <div class="vector-menu-heading"> Cloud VPS &amp; Toolforge </div> <div class="vector-menu-content"> <ul class="vector-menu-content-list"> <li id="n-Cloud-VPS-portal" class="mw-list-item"><a href="/wiki/Portal:Cloud_VPS"><span>Cloud VPS portal</span></a></li><li id="n-Toolforge-portal" class="mw-list-item"><a href="/wiki/Portal:Toolforge"><span>Toolforge portal</span></a></li><li id="n-Request-VPS-project" class="mw-list-item"><a href="https://phabricator.wikimedia.org/project/view/2875/"><span>Request VPS project</span></a></li><li id="n-Admin-log:-Cloud-VPS" class="mw-list-item"><a href="/wiki/Cloud_VPS_Server_Admin_Log"><span>Admin log: Cloud VPS</span></a></li> </ul> </div> </div> </div> </div> </div> </div> </nav> <a href="/wiki/Main_Page" class="mw-logo"> <img class="mw-logo-icon" src="/static/images/icons/wikitech.svg" alt="" aria-hidden="true" height="50" width="50"> <span class="mw-logo-container skin-invert"> <img class="mw-logo-wordmark" alt="Wikitech" src="/static/images/mobile/copyright/wikitech-wordmark.svg" style="width: 8.75em; height: 1.6875em;"> </span> </a> </div> <div class="vector-header-end"> <div id="p-search" role="search" class="vector-search-box-vue vector-search-box-collapses vector-search-box-show-thumbnail vector-search-box-auto-expand-width vector-search-box"> <a href="/wiki/Special:Search" class="cdx-button cdx-button--fake-button cdx-button--fake-button--enabled cdx-button--weight-quiet cdx-button--icon-only search-toggle" title="Search Wikitech [f]" accesskey="f"><span class="vector-icon mw-ui-icon-search mw-ui-icon-wikimedia-search"></span> <span>Search</span> </a> <div class="vector-typeahead-search-container"> <div class="cdx-typeahead-search cdx-typeahead-search--show-thumbnail cdx-typeahead-search--auto-expand-width"> <form action="/w/index.php" id="searchform" class="cdx-search-input cdx-search-input--has-end-button"> <div id="simpleSearch" class="cdx-search-input__input-wrapper" data-search-loc="header-moved"> <div class="cdx-text-input cdx-text-input--has-start-icon"> <input class="cdx-text-input__input" type="search" name="search" placeholder="Search Wikitech" aria-label="Search Wikitech" autocapitalize="sentences" title="Search Wikitech [f]" accesskey="f" id="searchInput" > <span class="cdx-text-input__icon cdx-text-input__start-icon"></span> </div> <input type="hidden" name="title" value="Special:Search"> </div> <button class="cdx-button cdx-search-input__end-button">Search</button> </form> </div> </div> </div> <nav class="vector-user-links vector-user-links-wide" aria-label="Personal tools"> <div class="vector-user-links-main"> <div id="p-vector-user-menu-preferences" class="vector-menu mw-portlet" > <div class="vector-menu-content"> <ul class="vector-menu-content-list"> <li id="ca-uls" class="mw-list-item active user-links-collapsible-item"><a data-mw="interface" href="#" class="uls-trigger cdx-button cdx-button--fake-button cdx-button--fake-button--enabled cdx-button--weight-quiet"><span class="vector-icon mw-ui-icon-wikimedia-language mw-ui-icon-wikimedia-wikimedia-language"></span> <span>English</span></a> </li> </ul> </div> </div> <div id="p-vector-user-menu-userpage" class="vector-menu mw-portlet emptyPortlet" > <div class="vector-menu-content"> <ul class="vector-menu-content-list"> </ul> </div> </div> <nav class="vector-appearance-landmark" aria-label="Appearance"> <div id="vector-appearance-dropdown" class="vector-dropdown " title="Change the appearance of the page&#039;s font size, width, and color" > <input type="checkbox" id="vector-appearance-dropdown-checkbox" role="button" aria-haspopup="true" data-event-name="ui.dropdown-vector-appearance-dropdown" class="vector-dropdown-checkbox " aria-label="Appearance" > <label id="vector-appearance-dropdown-label" for="vector-appearance-dropdown-checkbox" class="vector-dropdown-label cdx-button cdx-button--fake-button cdx-button--fake-button--enabled cdx-button--weight-quiet cdx-button--icon-only " aria-hidden="true" ><span class="vector-icon mw-ui-icon-appearance mw-ui-icon-wikimedia-appearance"></span> <span class="vector-dropdown-label-text">Appearance</span> </label> <div class="vector-dropdown-content"> <div id="vector-appearance-unpinned-container" class="vector-unpinned-container"> </div> </div> </div> </nav> <div id="p-vector-user-menu-notifications" class="vector-menu mw-portlet emptyPortlet" > <div class="vector-menu-content"> <ul class="vector-menu-content-list"> </ul> </div> </div> <div id="p-vector-user-menu-overflow" class="vector-menu mw-portlet" > <div class="vector-menu-content"> <ul class="vector-menu-content-list"> <li id="pt-sitesupport-2" class="user-links-collapsible-item mw-list-item user-links-collapsible-item"><a data-mw="interface" href="https://donate.wikimedia.org/?utm_source=donate&amp;utm_medium=sidebar&amp;utm_campaign=spontaneous&amp;uselang=en" class=""><span>Donate</span></a> </li> <li id="pt-login-2" class="user-links-collapsible-item mw-list-item user-links-collapsible-item"><a data-mw="interface" href="/w/index.php?title=Special:UserLogin&amp;returnto=MediaWiki+Engineering%2FGuides%2FBackend+performance+practices" title="You are encouraged to log in; however, it is not mandatory [o]" accesskey="o" class=""><span>Log in</span></a> </li> </ul> </div> </div> </div> <div id="vector-user-links-dropdown" class="vector-dropdown vector-user-menu vector-button-flush-right vector-user-menu-logged-out user-links-collapsible-item" title="More options" > <input type="checkbox" id="vector-user-links-dropdown-checkbox" role="button" aria-haspopup="true" data-event-name="ui.dropdown-vector-user-links-dropdown" class="vector-dropdown-checkbox " aria-label="Personal tools" > <label id="vector-user-links-dropdown-label" for="vector-user-links-dropdown-checkbox" class="vector-dropdown-label cdx-button cdx-button--fake-button cdx-button--fake-button--enabled cdx-button--weight-quiet cdx-button--icon-only " aria-hidden="true" ><span class="vector-icon mw-ui-icon-ellipsis mw-ui-icon-wikimedia-ellipsis"></span> <span class="vector-dropdown-label-text">Personal tools</span> </label> <div class="vector-dropdown-content"> <div id="p-personal" class="vector-menu mw-portlet mw-portlet-personal user-links-collapsible-item" title="User menu" > <div class="vector-menu-content"> <ul class="vector-menu-content-list"> <li id="pt-sitesupport" class="user-links-collapsible-item mw-list-item"><a href="https://donate.wikimedia.org/?utm_source=donate&amp;utm_medium=sidebar&amp;utm_campaign=spontaneous&amp;uselang=en"><span>Donate</span></a></li><li id="pt-login" class="user-links-collapsible-item mw-list-item"><a href="/w/index.php?title=Special:UserLogin&amp;returnto=MediaWiki+Engineering%2FGuides%2FBackend+performance+practices" title="You are encouraged to log in; however, it is not mandatory [o]" accesskey="o"><span class="vector-icon mw-ui-icon-logIn mw-ui-icon-wikimedia-logIn"></span> <span>Log in</span></a></li> </ul> </div> </div> </div> </div> </nav> </div> </header> </div> <div class="mw-page-container"> <div class="mw-page-container-inner"> <div class="vector-sitenotice-container"> <div id="siteNotice"><div id="mw-dismissablenotice-anonplace"></div><script>(function(){var node=document.getElementById("mw-dismissablenotice-anonplace");if(node){node.outerHTML="\u003Cdiv class=\"mw-dismissable-notice\"\u003E\u003Cdiv class=\"mw-dismissable-notice-close\"\u003E[\u003Ca tabindex=\"0\" role=\"button\"\u003Edismiss\u003C/a\u003E]\u003C/div\u003E\u003Cdiv class=\"mw-dismissable-notice-body\"\u003E\u003C!-- CentralNotice --\u003E\u003Cdiv id=\"localNotice\" data-nosnippet=\"\"\u003E\u003Cdiv class=\"sitenotice\" lang=\"en\" dir=\"ltr\"\u003E\u003Ctable style=\"width: 75%; background-color: var(--background-color-warning-subtle, #fdf2d5); border: var(--border-subtle, 1px solid #987027); color: var(--color-base, #202122); border-radius: 10px; padding: 5px; margin: 0 auto;\"\u003E\n\u003Ctbody\u003E\u003Ctr\u003E\n\u003Ctd style=\"width:40px; height:40px; text-align:center; vertical-align:middle; padding: 2px;\"\u003E\u003Cspan typeof=\"mw:File\"\u003E\u003Ca href=\"/wiki/File:OOjs_UI_icon_alert-warning.svg\" class=\"mw-file-description\"\u003E\u003Cimg src=\"//upload.wikimedia.org/wikipedia/commons/thumb/3/3b/OOjs_UI_icon_alert-warning.svg/30px-OOjs_UI_icon_alert-warning.svg.png\" decoding=\"async\" width=\"30\" height=\"30\" class=\"mw-file-element\" srcset=\"//upload.wikimedia.org/wikipedia/commons/thumb/3/3b/OOjs_UI_icon_alert-warning.svg/45px-OOjs_UI_icon_alert-warning.svg.png 1.5x, //upload.wikimedia.org/wikipedia/commons/thumb/3/3b/OOjs_UI_icon_alert-warning.svg/60px-OOjs_UI_icon_alert-warning.svg.png 2x\" data-file-width=\"20\" data-file-height=\"20\" /\u003E\u003C/a\u003E\u003C/span\u003E\n\u003C/td\u003E\n\u003Ctd style=\"text-align:center; vertical-align: middle; padding: 4px; max-height: 60px;\"\u003E\u003Cb\u003EWe are migrating Wikitech to \u003Ca href=\"/wiki/Wikitech/SUL-migration\" title=\"Wikitech/SUL-migration\"\u003ESUL\u003C/a\u003E!\u003C/b\u003E\n\u003Cp\u003E\u003Cb\u003EAction may be required for your \u003Ca href=\"/wiki/Wikitech/SUL-migration#What_You_Should_Do\" title=\"Wikitech/SUL-migration\"\u003E account\u003C/a\u003E!\u003C/b\u003E\n\u003C/p\u003E\u003Cp\u003E\u003Cb\u003ETrouble logging in? Please visit \u003Ca href=\"https://phabricator.wikimedia.org/T376267\" class=\"extiw\" title=\"phab:T376267\"\u003ET376267\u003C/a\u003E\u003C/b\u003E\n\u003C/p\u003E\n\u003C/td\u003E\u003C/tr\u003E\u003C/tbody\u003E\u003C/table\u003E\u003C/div\u003E\u003C/div\u003E\u003C/div\u003E\u003C/div\u003E";}}());</script></div> </div> <div class="vector-column-start"> <div class="vector-main-menu-container"> <div id="mw-navigation"> <nav id="mw-panel" class="vector-main-menu-landmark" aria-label="Site"> <div id="vector-main-menu-pinned-container" class="vector-pinned-container"> </div> </nav> </div> </div> <div class="vector-sticky-pinned-container"> <nav id="mw-panel-toc" aria-label="Contents" data-event-name="ui.sidebar-toc" class="mw-table-of-contents-container vector-toc-landmark"> <div id="vector-toc-pinned-container" class="vector-pinned-container"> <div id="vector-toc" class="vector-toc vector-pinnable-element"> <div class="vector-pinnable-header vector-toc-pinnable-header vector-pinnable-header-pinned" data-feature-name="toc-pinned" data-pinnable-element-id="vector-toc" > <h2 class="vector-pinnable-header-label">Contents</h2> <button class="vector-pinnable-header-toggle-button vector-pinnable-header-pin-button" data-event-name="pinnable-header.vector-toc.pin">move to sidebar</button> <button class="vector-pinnable-header-toggle-button vector-pinnable-header-unpin-button" data-event-name="pinnable-header.vector-toc.unpin">hide</button> </div> <ul class="vector-toc-contents" id="mw-panel-toc-list"> <li id="toc-mw-content-text" class="vector-toc-list-item vector-toc-level-1"> <a href="#" class="vector-toc-link"> <div class="vector-toc-text">Beginning</div> </a> </li> <li id="toc-Getting_started" class="vector-toc-list-item vector-toc-level-1"> <a class="vector-toc-link" href="#Getting_started"> <div class="vector-toc-text"> <span class="vector-toc-numb">1</span> <span>Getting started</span> </div> </a> <button aria-controls="toc-Getting_started-sublist" class="cdx-button cdx-button--weight-quiet cdx-button--icon-only vector-toc-toggle"> <span class="vector-icon mw-ui-icon-wikimedia-expand"></span> <span>Toggle Getting started subsection</span> </button> <ul id="toc-Getting_started-sublist" class="vector-toc-list"> <li id="toc-General_principles" class="vector-toc-list-item vector-toc-level-2"> <a class="vector-toc-link" href="#General_principles"> <div class="vector-toc-text"> <span class="vector-toc-numb">1.1</span> <span>General principles</span> </div> </a> <ul id="toc-General_principles-sublist" class="vector-toc-list"> </ul> </li> <li id="toc-Ballpark_numbers" class="vector-toc-list-item vector-toc-level-2"> <a class="vector-toc-link" href="#Ballpark_numbers"> <div class="vector-toc-text"> <span class="vector-toc-numb">1.2</span> <span>Ballpark numbers</span> </div> </a> <ul id="toc-Ballpark_numbers-sublist" class="vector-toc-list"> </ul> </li> <li id="toc-Percentiles" class="vector-toc-list-item vector-toc-level-2"> <a class="vector-toc-link" href="#Percentiles"> <div class="vector-toc-text"> <span class="vector-toc-numb">1.3</span> <span>Percentiles</span> </div> </a> <ul id="toc-Percentiles-sublist" class="vector-toc-list"> </ul> </li> <li id="toc-Latency" class="vector-toc-list-item vector-toc-level-2"> <a class="vector-toc-link" href="#Latency"> <div class="vector-toc-text"> <span class="vector-toc-numb">1.4</span> <span>Latency</span> </div> </a> <ul id="toc-Latency-sublist" class="vector-toc-list"> </ul> </li> </ul> </li> <li id="toc-How_often_will_my_code_run?" class="vector-toc-list-item vector-toc-level-1"> <a class="vector-toc-link" href="#How_often_will_my_code_run?"> <div class="vector-toc-text"> <span class="vector-toc-numb">2</span> <span>How often will my code run?</span> </div> </a> <button aria-controls="toc-How_often_will_my_code_run?-sublist" class="cdx-button cdx-button--weight-quiet cdx-button--icon-only vector-toc-toggle"> <span class="vector-icon mw-ui-icon-wikimedia-expand"></span> <span>Toggle How often will my code run? subsection</span> </button> <ul id="toc-How_often_will_my_code_run?-sublist" class="vector-toc-list"> <li id="toc-Caching_layers" class="vector-toc-list-item vector-toc-level-2"> <a class="vector-toc-link" href="#Caching_layers"> <div class="vector-toc-text"> <span class="vector-toc-numb">2.1</span> <span>Caching layers</span> </div> </a> <ul id="toc-Caching_layers-sublist" class="vector-toc-list"> </ul> </li> <li id="toc-Cookies" class="vector-toc-list-item vector-toc-level-2"> <a class="vector-toc-link" href="#Cookies"> <div class="vector-toc-text"> <span class="vector-toc-numb">2.2</span> <span>Cookies</span> </div> </a> <ul id="toc-Cookies-sublist" class="vector-toc-list"> </ul> </li> <li id="toc-Design_for_cache_miss" class="vector-toc-list-item vector-toc-level-2"> <a class="vector-toc-link" href="#Design_for_cache_miss"> <div class="vector-toc-text"> <span class="vector-toc-numb">2.3</span> <span>Design for cache miss</span> </div> </a> <ul id="toc-Design_for_cache_miss-sublist" class="vector-toc-list"> </ul> </li> <li id="toc-Practical_examples_about_caching_layers" class="vector-toc-list-item vector-toc-level-2"> <a class="vector-toc-link" href="#Practical_examples_about_caching_layers"> <div class="vector-toc-text"> <span class="vector-toc-numb">2.4</span> <span>Practical examples about caching layers</span> </div> </a> <ul id="toc-Practical_examples_about_caching_layers-sublist" class="vector-toc-list"> </ul> </li> </ul> </li> <li id="toc-Leverage_the_platform!" class="vector-toc-list-item vector-toc-level-1"> <a class="vector-toc-link" href="#Leverage_the_platform!"> <div class="vector-toc-text"> <span class="vector-toc-numb">3</span> <span>Leverage the platform!</span> </div> </a> <ul id="toc-Leverage_the_platform!-sublist" class="vector-toc-list"> </ul> </li> <li id="toc-You_are_not_alone" class="vector-toc-list-item vector-toc-level-1"> <a class="vector-toc-link" href="#You_are_not_alone"> <div class="vector-toc-text"> <span class="vector-toc-numb">4</span> <span>You are not alone</span> </div> </a> <ul id="toc-You_are_not_alone-sublist" class="vector-toc-list"> </ul> </li> <li id="toc-Persistence_layer" class="vector-toc-list-item vector-toc-level-1"> <a class="vector-toc-link" href="#Persistence_layer"> <div class="vector-toc-text"> <span class="vector-toc-numb">5</span> <span>Persistence layer</span> </div> </a> <button aria-controls="toc-Persistence_layer-sublist" class="cdx-button cdx-button--weight-quiet cdx-button--icon-only vector-toc-toggle"> <span class="vector-icon mw-ui-icon-wikimedia-expand"></span> <span>Toggle Persistence layer subsection</span> </button> <ul id="toc-Persistence_layer-sublist" class="vector-toc-list"> <li id="toc-Schema_and_API_design" class="vector-toc-list-item vector-toc-level-2"> <a class="vector-toc-link" href="#Schema_and_API_design"> <div class="vector-toc-text"> <span class="vector-toc-numb">5.1</span> <span>Schema and API design</span> </div> </a> <ul id="toc-Schema_and_API_design-sublist" class="vector-toc-list"> </ul> </li> <li id="toc-Practical_examples_of_persistence" class="vector-toc-list-item vector-toc-level-2"> <a class="vector-toc-link" href="#Practical_examples_of_persistence"> <div class="vector-toc-text"> <span class="vector-toc-numb">5.2</span> <span>Practical examples of persistence</span> </div> </a> <ul id="toc-Practical_examples_of_persistence-sublist" class="vector-toc-list"> <li id="toc-Example:_Permanent_names" class="vector-toc-list-item vector-toc-level-3"> <a class="vector-toc-link" href="#Example:_Permanent_names"> <div class="vector-toc-text"> <span class="vector-toc-numb">5.2.1</span> <span>Example: Permanent names</span> </div> </a> <ul id="toc-Example:_Permanent_names-sublist" class="vector-toc-list"> </ul> </li> <li id="toc-Example:_Large_object_size" class="vector-toc-list-item vector-toc-level-3"> <a class="vector-toc-link" href="#Example:_Large_object_size"> <div class="vector-toc-text"> <span class="vector-toc-numb">5.2.2</span> <span>Example: Large object size</span> </div> </a> <ul id="toc-Example:_Large_object_size-sublist" class="vector-toc-list"> </ul> </li> <li id="toc-Example:_Job_queue_use_cases" class="vector-toc-list-item vector-toc-level-3"> <a class="vector-toc-link" href="#Example:_Job_queue_use_cases"> <div class="vector-toc-text"> <span class="vector-toc-numb">5.2.3</span> <span>Example: Job queue use cases</span> </div> </a> <ul id="toc-Example:_Job_queue_use_cases-sublist" class="vector-toc-list"> </ul> </li> <li id="toc-Example:_Send_a_notification" class="vector-toc-list-item vector-toc-level-3"> <a class="vector-toc-link" href="#Example:_Send_a_notification"> <div class="vector-toc-text"> <span class="vector-toc-numb">5.2.4</span> <span>Example: Send a notification</span> </div> </a> <ul id="toc-Example:_Send_a_notification-sublist" class="vector-toc-list"> </ul> </li> <li id="toc-Example:_Count_active_users" class="vector-toc-list-item vector-toc-level-3"> <a class="vector-toc-link" href="#Example:_Count_active_users"> <div class="vector-toc-text"> <span class="vector-toc-numb">5.2.5</span> <span>Example: Count active users</span> </div> </a> <ul id="toc-Example:_Count_active_users-sublist" class="vector-toc-list"> </ul> </li> </ul> </li> <li id="toc-Indexing" class="vector-toc-list-item vector-toc-level-2"> <a class="vector-toc-link" href="#Indexing"> <div class="vector-toc-text"> <span class="vector-toc-numb">5.3</span> <span>Indexing</span> </div> </a> <ul id="toc-Indexing-sublist" class="vector-toc-list"> </ul> </li> <li id="toc-Multiple_datacenters" class="vector-toc-list-item vector-toc-level-2"> <a class="vector-toc-link" href="#Multiple_datacenters"> <div class="vector-toc-text"> <span class="vector-toc-numb">5.4</span> <span>Multiple datacenters</span> </div> </a> <ul id="toc-Multiple_datacenters-sublist" class="vector-toc-list"> </ul> </li> </ul> </li> <li id="toc-Shared_resources" class="vector-toc-list-item vector-toc-level-1"> <a class="vector-toc-link" href="#Shared_resources"> <div class="vector-toc-text"> <span class="vector-toc-numb">6</span> <span>Shared resources</span> </div> </a> <button aria-controls="toc-Shared_resources-sublist" class="cdx-button cdx-button--weight-quiet cdx-button--icon-only vector-toc-toggle"> <span class="vector-icon mw-ui-icon-wikimedia-expand"></span> <span>Toggle Shared resources subsection</span> </button> <ul id="toc-Shared_resources-sublist" class="vector-toc-list"> <li id="toc-Transactions" class="vector-toc-list-item vector-toc-level-2"> <a class="vector-toc-link" href="#Transactions"> <div class="vector-toc-text"> <span class="vector-toc-numb">6.1</span> <span>Transactions</span> </div> </a> <ul id="toc-Transactions-sublist" class="vector-toc-list"> </ul> </li> <li id="toc-Long-running_queries" class="vector-toc-list-item vector-toc-level-2"> <a class="vector-toc-link" href="#Long-running_queries"> <div class="vector-toc-text"> <span class="vector-toc-numb">6.2</span> <span>Long-running queries</span> </div> </a> <ul id="toc-Long-running_queries-sublist" class="vector-toc-list"> </ul> </li> <li id="toc-Advisory_locks" class="vector-toc-list-item vector-toc-level-2"> <a class="vector-toc-link" href="#Advisory_locks"> <div class="vector-toc-text"> <span class="vector-toc-numb">6.3</span> <span>Advisory locks</span> </div> </a> <ul id="toc-Advisory_locks-sublist" class="vector-toc-list"> </ul> </li> <li id="toc-Rate_limiting" class="vector-toc-list-item vector-toc-level-2"> <a class="vector-toc-link" href="#Rate_limiting"> <div class="vector-toc-text"> <span class="vector-toc-numb">6.4</span> <span>Rate limiting</span> </div> </a> <ul id="toc-Rate_limiting-sublist" class="vector-toc-list"> </ul> </li> </ul> </li> <li id="toc-Further_reading" class="vector-toc-list-item vector-toc-level-1"> <a class="vector-toc-link" href="#Further_reading"> <div class="vector-toc-text"> <span class="vector-toc-numb">7</span> <span>Further reading</span> </div> </a> <button aria-controls="toc-Further_reading-sublist" class="cdx-button cdx-button--weight-quiet cdx-button--icon-only vector-toc-toggle"> <span class="vector-icon mw-ui-icon-wikimedia-expand"></span> <span>Toggle Further reading subsection</span> </button> <ul id="toc-Further_reading-sublist" class="vector-toc-list"> <li id="toc-Articles" class="vector-toc-list-item vector-toc-level-2"> <a class="vector-toc-link" href="#Articles"> <div class="vector-toc-text"> <span class="vector-toc-numb">7.1</span> <span>Articles</span> </div> </a> <ul id="toc-Articles-sublist" class="vector-toc-list"> </ul> </li> <li id="toc-Talks" class="vector-toc-list-item vector-toc-level-2"> <a class="vector-toc-link" href="#Talks"> <div class="vector-toc-text"> <span class="vector-toc-numb">7.2</span> <span>Talks</span> </div> </a> <ul id="toc-Talks-sublist" class="vector-toc-list"> </ul> </li> <li id="toc-ResourceLoader" class="vector-toc-list-item vector-toc-level-2"> <a class="vector-toc-link" href="#ResourceLoader"> <div class="vector-toc-text"> <span class="vector-toc-numb">7.3</span> <span>ResourceLoader</span> </div> </a> <ul id="toc-ResourceLoader-sublist" class="vector-toc-list"> </ul> </li> <li id="toc-Meta" class="vector-toc-list-item vector-toc-level-2"> <a class="vector-toc-link" href="#Meta"> <div class="vector-toc-text"> <span class="vector-toc-numb">7.4</span> <span>Meta</span> </div> </a> <ul id="toc-Meta-sublist" class="vector-toc-list"> </ul> </li> </ul> </li> <li id="toc-References" class="vector-toc-list-item vector-toc-level-1"> <a class="vector-toc-link" href="#References"> <div class="vector-toc-text"> <span class="vector-toc-numb">8</span> <span>References</span> </div> </a> <ul id="toc-References-sublist" class="vector-toc-list"> </ul> </li> </ul> </div> </div> </nav> </div> </div> <div class="mw-content-container"> <main id="content" class="mw-body"> <header class="mw-body-header vector-page-titlebar"> <nav aria-label="Contents" class="vector-toc-landmark"> <div id="vector-page-titlebar-toc" class="vector-dropdown vector-page-titlebar-toc vector-button-flush-left" > <input type="checkbox" id="vector-page-titlebar-toc-checkbox" role="button" aria-haspopup="true" data-event-name="ui.dropdown-vector-page-titlebar-toc" class="vector-dropdown-checkbox " aria-label="Toggle the table of contents" > <label id="vector-page-titlebar-toc-label" for="vector-page-titlebar-toc-checkbox" class="vector-dropdown-label cdx-button cdx-button--fake-button cdx-button--fake-button--enabled cdx-button--weight-quiet cdx-button--icon-only " aria-hidden="true" ><span class="vector-icon mw-ui-icon-listBullet mw-ui-icon-wikimedia-listBullet"></span> <span class="vector-dropdown-label-text">Toggle the table of contents</span> </label> <div class="vector-dropdown-content"> <div id="vector-page-titlebar-toc-unpinned-container" class="vector-unpinned-container"> </div> </div> </div> </nav> <h1 id="firstHeading" class="firstHeading mw-first-heading"><span class="mw-page-title-main">MediaWiki Engineering/Guides/Backend performance practices</span></h1> </header> <div class="vector-page-toolbar"> <div class="vector-page-toolbar-container"> <div id="left-navigation"> <nav aria-label="Namespaces"> <div id="p-associated-pages" class="vector-menu vector-menu-tabs mw-portlet mw-portlet-associated-pages" > <div class="vector-menu-content"> <ul class="vector-menu-content-list"> <li id="ca-nstab-main" class="selected vector-tab-noicon mw-list-item"><a href="/wiki/MediaWiki_Engineering/Guides/Backend_performance_practices" title="View the content page [c]" accesskey="c"><span>Page</span></a></li><li id="ca-talk" class="vector-tab-noicon mw-list-item"><a href="/wiki/Talk:MediaWiki_Engineering/Guides/Backend_performance_practices" rel="discussion" title="Discussion about the content page [t]" accesskey="t"><span>Discussion</span></a></li> </ul> </div> </div> <div id="vector-variants-dropdown" class="vector-dropdown emptyPortlet" > <input type="checkbox" id="vector-variants-dropdown-checkbox" role="button" aria-haspopup="true" data-event-name="ui.dropdown-vector-variants-dropdown" class="vector-dropdown-checkbox " aria-label="Change language variant" > <label id="vector-variants-dropdown-label" for="vector-variants-dropdown-checkbox" class="vector-dropdown-label cdx-button cdx-button--fake-button cdx-button--fake-button--enabled cdx-button--weight-quiet" aria-hidden="true" ><span class="vector-dropdown-label-text">English</span> </label> <div class="vector-dropdown-content"> <div id="p-variants" class="vector-menu mw-portlet mw-portlet-variants emptyPortlet" > <div class="vector-menu-content"> <ul class="vector-menu-content-list"> </ul> </div> </div> </div> </div> </nav> </div> <div id="right-navigation" class="vector-collapsible"> <nav aria-label="Views"> <div id="p-views" class="vector-menu vector-menu-tabs mw-portlet mw-portlet-views" > <div class="vector-menu-content"> <ul class="vector-menu-content-list"> <li id="ca-view" class="selected vector-tab-noicon mw-list-item"><a href="/wiki/MediaWiki_Engineering/Guides/Backend_performance_practices"><span>Read</span></a></li><li id="ca-viewsource" class="vector-tab-noicon mw-list-item"><a href="/w/index.php?title=MediaWiki_Engineering/Guides/Backend_performance_practices&amp;action=edit" title="This page is protected.&#10;You can view its source [e]" accesskey="e"><span>View source</span></a></li><li id="ca-history" class="vector-tab-noicon mw-list-item"><a href="/w/index.php?title=MediaWiki_Engineering/Guides/Backend_performance_practices&amp;action=history" title="Past revisions of this page [h]" accesskey="h"><span>View history</span></a></li> </ul> </div> </div> </nav> <nav class="vector-page-tools-landmark" aria-label="Page tools"> <div id="vector-page-tools-dropdown" class="vector-dropdown vector-page-tools-dropdown" > <input type="checkbox" id="vector-page-tools-dropdown-checkbox" role="button" aria-haspopup="true" data-event-name="ui.dropdown-vector-page-tools-dropdown" class="vector-dropdown-checkbox " aria-label="Tools" > <label id="vector-page-tools-dropdown-label" for="vector-page-tools-dropdown-checkbox" class="vector-dropdown-label cdx-button cdx-button--fake-button cdx-button--fake-button--enabled cdx-button--weight-quiet" aria-hidden="true" ><span class="vector-dropdown-label-text">Tools</span> </label> <div class="vector-dropdown-content"> <div id="vector-page-tools-unpinned-container" class="vector-unpinned-container"> <div id="vector-page-tools" class="vector-page-tools vector-pinnable-element"> <div class="vector-pinnable-header vector-page-tools-pinnable-header vector-pinnable-header-unpinned" data-feature-name="page-tools-pinned" data-pinnable-element-id="vector-page-tools" data-pinned-container-id="vector-page-tools-pinned-container" data-unpinned-container-id="vector-page-tools-unpinned-container" > <div class="vector-pinnable-header-label">Tools</div> <button class="vector-pinnable-header-toggle-button vector-pinnable-header-pin-button" data-event-name="pinnable-header.vector-page-tools.pin">move to sidebar</button> <button class="vector-pinnable-header-toggle-button vector-pinnable-header-unpin-button" data-event-name="pinnable-header.vector-page-tools.unpin">hide</button> </div> <div id="p-cactions" class="vector-menu mw-portlet mw-portlet-cactions emptyPortlet vector-has-collapsible-items" title="More options" > <div class="vector-menu-heading"> Actions </div> <div class="vector-menu-content"> <ul class="vector-menu-content-list"> <li id="ca-more-view" class="selected vector-more-collapsible-item mw-list-item"><a href="/wiki/MediaWiki_Engineering/Guides/Backend_performance_practices"><span>Read</span></a></li><li id="ca-more-viewsource" class="vector-more-collapsible-item mw-list-item"><a href="/w/index.php?title=MediaWiki_Engineering/Guides/Backend_performance_practices&amp;action=edit"><span>View source</span></a></li><li id="ca-more-history" class="vector-more-collapsible-item mw-list-item"><a href="/w/index.php?title=MediaWiki_Engineering/Guides/Backend_performance_practices&amp;action=history"><span>View history</span></a></li> </ul> </div> </div> <div id="p-tb" class="vector-menu mw-portlet mw-portlet-tb" > <div class="vector-menu-heading"> General </div> <div class="vector-menu-content"> <ul class="vector-menu-content-list"> <li id="t-whatlinkshere" class="mw-list-item"><a href="/wiki/Special:WhatLinksHere/MediaWiki_Engineering/Guides/Backend_performance_practices" title="A list of all wiki pages that link here [j]" accesskey="j"><span>What links here</span></a></li><li id="t-recentchangeslinked" class="mw-list-item"><a href="/wiki/Special:RecentChangesLinked/MediaWiki_Engineering/Guides/Backend_performance_practices" rel="nofollow" title="Recent changes in pages linked from this page [k]" accesskey="k"><span>Related changes</span></a></li><li id="t-specialpages" class="mw-list-item"><a href="/wiki/Special:SpecialPages" title="A list of all special pages [q]" accesskey="q"><span>Special pages</span></a></li><li id="t-permalink" class="mw-list-item"><a href="/w/index.php?title=MediaWiki_Engineering/Guides/Backend_performance_practices&amp;oldid=2233113" title="Permanent link to this revision of this page"><span>Permanent link</span></a></li><li id="t-info" class="mw-list-item"><a href="/w/index.php?title=MediaWiki_Engineering/Guides/Backend_performance_practices&amp;action=info" title="More information about this page"><span>Page information</span></a></li><li id="t-cite" class="mw-list-item"><a href="/w/index.php?title=Special:CiteThisPage&amp;page=MediaWiki_Engineering%2FGuides%2FBackend_performance_practices&amp;id=2233113&amp;wpFormIdentifier=titleform" title="Information on how to cite this page"><span>Cite this page</span></a></li><li id="t-urlshortener" class="mw-list-item"><a href="/w/index.php?title=Special:UrlShortener&amp;url=https%3A%2F%2Fwikitech.wikimedia.org%2Fwiki%2FMediaWiki_Engineering%2FGuides%2FBackend_performance_practices"><span>Get shortened URL</span></a></li><li id="t-urlshortener-qrcode" class="mw-list-item"><a href="/w/index.php?title=Special:QrCode&amp;url=https%3A%2F%2Fwikitech.wikimedia.org%2Fwiki%2FMediaWiki_Engineering%2FGuides%2FBackend_performance_practices"><span>Download QR code</span></a></li> </ul> </div> </div> <div id="p-coll-print_export" class="vector-menu mw-portlet mw-portlet-coll-print_export" > <div class="vector-menu-heading"> Print/export </div> <div class="vector-menu-content"> <ul class="vector-menu-content-list"> <li id="coll-create_a_book" class="mw-list-item"><a href="/w/index.php?title=Special:Book&amp;bookcmd=book_creator&amp;referer=MediaWiki+Engineering%2FGuides%2FBackend+performance+practices"><span>Create a book</span></a></li><li id="coll-download-as-rl" class="mw-list-item"><a href="/w/index.php?title=Special:DownloadAsPdf&amp;page=MediaWiki_Engineering%2FGuides%2FBackend_performance_practices&amp;action=show-download-screen"><span>Download as PDF</span></a></li><li id="t-print" class="mw-list-item"><a href="/w/index.php?title=MediaWiki_Engineering/Guides/Backend_performance_practices&amp;printable=yes" title="Printable version of this page [p]" accesskey="p"><span>Printable version</span></a></li> </ul> </div> </div> </div> </div> </div> </div> </nav> </div> </div> </div> <div class="vector-column-end"> <div class="vector-sticky-pinned-container"> <nav class="vector-page-tools-landmark" aria-label="Page tools"> <div id="vector-page-tools-pinned-container" class="vector-pinned-container"> </div> </nav> <nav class="vector-appearance-landmark" aria-label="Appearance"> <div id="vector-appearance-pinned-container" class="vector-pinned-container"> <div id="vector-appearance" class="vector-appearance vector-pinnable-element"> <div class="vector-pinnable-header vector-appearance-pinnable-header vector-pinnable-header-pinned" data-feature-name="appearance-pinned" data-pinnable-element-id="vector-appearance" data-pinned-container-id="vector-appearance-pinned-container" data-unpinned-container-id="vector-appearance-unpinned-container" > <div class="vector-pinnable-header-label">Appearance</div> <button class="vector-pinnable-header-toggle-button vector-pinnable-header-pin-button" data-event-name="pinnable-header.vector-appearance.pin">move to sidebar</button> <button class="vector-pinnable-header-toggle-button vector-pinnable-header-unpin-button" data-event-name="pinnable-header.vector-appearance.unpin">hide</button> </div> </div> </div> </nav> </div> </div> <div id="bodyContent" class="vector-body" aria-labelledby="firstHeading" data-mw-ve-target-container> <div class="vector-body-before-content"> <div class="mw-indicators"> </div> <div id="siteSub" class="noprint">From Wikitech</div> </div> <div id="contentSub"><div id="mw-content-subtitle"><div class="subpages">&lt; <bdi dir="ltr"><a href="/wiki/MediaWiki_Engineering" title="MediaWiki Engineering">MediaWiki Engineering</a></bdi></div><span class="mw-redirectedfrom">(Redirected from <a href="/w/index.php?title=Performance/Guides/Backend_performance_practices&amp;redirect=no" class="mw-redirect" title="Performance/Guides/Backend performance practices">Performance/Guides/Backend performance practices</a>)</span></div></div> <div id="mw-content-text" class="mw-body-content"><div class="mw-content-ltr mw-parser-output" lang="en" dir="ltr"><style data-mw-deduplicate="TemplateStyles:r2230541">.mw-parser-output .tpl-navinfra--portal{float:none}.mw-parser-output .tpl-navinfra--portal .tpl-navsidebar-contents{margin:0 8px;display:flex;flex-flow:row wrap;gap:16px}.mw-parser-output .tpl-navinfra--portal .tpl-navsidebar-content{break-inside:avoid}.mw-parser-output .tpl-navinfra--sidebar .tpl-navsidebar-content{margin:0}.mw-parser-output .tpl-navinfra--sidebar .tpl-navsidebar-heading{font-weight:normal;margin:1px 0 0 0}.mw-parser-output .tpl-navinfra--sidebar .tpl-navsidebar-heading a{display:block;border-top:1px solid var(--border-color-muted,#eaecf0);background-color:var(--background-color-neutral-subtle,#f8f9fa);color:var(--color-base,#202122);margin:0 -8px;padding:4px 8px 0px 8px}.mw-parser-output .tpl-navinfra--sidebar .tpl-navsidebar-heading a:hover{background-color:var(--background-color-base,#fff);color:var(--color-emphasized,#000)}.mw-parser-output ul{margin-top:unset!important}</style> <style data-mw-deduplicate="TemplateStyles:r2241375">.mw-parser-output .tpl-navsidebar{max-width:22em;background:var(--background-color-base,#fff);color:var(--color-base,#202122);border:1px solid var(--border-color-base,#a2a9b1);float:right;clear:right;margin:.5em 0 1em 1em}.mw-parser-output .tpl-navsidebar-floatright{float:right;clear:right;margin:.5em 0 1em 1em}.mw-parser-output .tpl-navsidebar-floatleft{float:left;clear:left;margin:.5em 1em 1em 0}.mw-parser-output .tpl-navsidebar-floatnone{float:none;clear:both;margin:.5em 0}.mw-parser-output .tpl-navsidebar-topimage{margin:0 0 16px 0}.mw-parser-output .tpl-navsidebar-title{margin:8px 16px;border-bottom:3px solid var(--border-color-muted,#eaecf0);font-size:20px;text-align:center}.mw-parser-output .tpl-navsidebar-image{margin:0 0 8px}.mw-parser-output .tpl-navsidebar-content{margin:0 0 16px 0;padding:0 8px}.mw-parser-output .tpl-navsidebar-heading{margin:8px 0;font-weight:bold}.mw-parser-output .tpl-navsidebar-foot{padding:0 8px;margin:0;text-align:right;font-size:smaller}@media not (min-width:720px){.mw-parser-output .tpl-navsidebar{float:none;clear:both;margin:.5em 0;max-width:none}}</style><div role="navigation" class="navigation-not-searchable tpl-navsidebar tpl-navinfra--sidebar" style=""><p class="tpl-navsidebar-title"><a href="/wiki/Wikimedia_infrastructure" title="Wikimedia infrastructure">Wikimedia infrastructure</a></p><div class="tpl-navsidebar-contents"><div class="tpl-navsidebar-content"> <p class="tpl-navsidebar-heading"><a href="/wiki/Data_centers" title="Data centers">Data centers</a></p><p class="mw-empty-elt"> </p></div><div class="tpl-navsidebar-content"> <p class="tpl-navsidebar-heading"><a href="/wiki/Network_design" title="Network design">Networking</a></p><p class="mw-empty-elt"> </p></div><div class="tpl-navsidebar-content"> <p class="tpl-navsidebar-heading"><a href="/wiki/Global_traffic_routing" title="Global traffic routing">Global traffic routing</a></p><p class="mw-empty-elt"> </p></div><div class="tpl-navsidebar-content"> <p class="tpl-navsidebar-heading"><a href="/wiki/MediaWiki_at_WMF" title="MediaWiki at WMF">MediaWiki SRE</a></p><p class="mw-empty-elt"> </p></div><div class="tpl-navsidebar-content"> <p class="tpl-navsidebar-heading"><a href="/wiki/MediaWiki_Engineering" title="MediaWiki Engineering">MediaWiki Engineering</a></p><p class="mw-empty-elt"> </p><ul><li><a href="/wiki/Parser_cache" title="Parser cache">Parser cache</a></li> <li><a href="/wiki/MediaWiki_JobQueue" title="MediaWiki JobQueue">MediaWiki JobQueue</a></li> <li><a href="/wiki/MediaWiki_Engineering/Performance_Review" title="MediaWiki Engineering/Performance Review">Performance review</a></li> <li><a href="/wiki/Performance.wikimedia.org" title="Performance.wikimedia.org">performance.wikimedia.org</a></li> <li><a href="/wiki/Web_Perf_Hero_award" title="Web Perf Hero award">Web Perf Hero award</a></li> <li>Guides: <ul><li><a href="/wiki/MediaWiki_Engineering/Guides/Frontend_performance_practices" title="MediaWiki Engineering/Guides/Frontend performance practices">Frontend best practices</a></li> <li><a class="mw-selflink selflink">Backend best practices</a></li> <li><i><a href="/wiki/MediaWiki_Engineering#Guides" title="MediaWiki Engineering">more…</a></i></li></ul></li></ul> </div><div class="tpl-navsidebar-content"> <p class="tpl-navsidebar-heading"><a href="/wiki/Upload.wikimedia.org" title="Upload.wikimedia.org">Multimedia</a></p><p class="mw-empty-elt"> </p></div><div class="tpl-navsidebar-content"> <p class="tpl-navsidebar-heading"><a href="/wiki/Data_Engineering" class="mw-redirect" title="Data Engineering">Data Engineering</a></p><p class="mw-empty-elt"> </p></div><div class="tpl-navsidebar-content"> <p class="tpl-navsidebar-heading"><a href="/wiki/Search" title="Search">Search</a></p><p class="mw-empty-elt"> </p></div><div class="tpl-navsidebar-content"> <p class="tpl-navsidebar-heading"><a href="/wiki/SRE/Data_Persistence/Documentation" title="SRE/Data Persistence/Documentation">SRE Data Persistence</a></p><p class="mw-empty-elt"> </p></div><div class="tpl-navsidebar-content"> <p class="tpl-navsidebar-heading"><a href="/wiki/SRE/Infrastructure_Foundations/Documentation" title="SRE/Infrastructure Foundations/Documentation">SRE Infra Foundations</a></p><p class="mw-empty-elt"> </p></div><div class="tpl-navsidebar-content"> <p class="tpl-navsidebar-heading"><a href="/wiki/SRE/Observability/Documentation" title="SRE/Observability/Documentation">SRE Observability</a></p><p class="mw-empty-elt"> </p></div><div class="tpl-navsidebar-content"> <p class="tpl-navsidebar-heading"><a href="/wiki/Performance" title="Performance">Wikimedia Performance</a></p><p class="mw-empty-elt"> </p></div><div class="tpl-navsidebar-content"> <p class="tpl-navsidebar-heading"><a href="/wiki/Event_Platform" title="Event Platform">Event Platform</a></p><p class="mw-empty-elt"> </p></div><div class="tpl-navsidebar-content"> <p class="tpl-navsidebar-heading"><a href="/wiki/Release_Engineering" title="Release Engineering">Release Engineering</a></p><p class="mw-empty-elt"> </p></div></div><p class="tpl-navsidebar-foot">[<span class="noprint plainlinks"><a class="external text" href="https://wikitech.wikimedia.org/w/index.php?title=Template:Navigation_Wikimedia_infrastructure&amp;action=edit"><span title="Edit this template">edit</span></a></span>]</p></div> <p>These are the performance guidelines for <b>MediaWiki backend development</b> aimed at being deployed to Wikimedia Foundation wikis. Use the below guidelines together with the <a href="https://www.mediawiki.org/wiki/Architecture_guidelines" class="extiw" title="mw:Architecture guidelines">Architecture guidelines</a> and <a href="https://www.mediawiki.org/wiki/Security_for_developers/Architecture" class="extiw" title="mw:Security for developers/Architecture">Security guidelines</a>. </p> <div class="mw-heading mw-heading2 ext-discussiontools-init-section"><h2 id="Getting_started" data-mw-thread-id="h-Getting_started"><span data-mw-comment-start="" id="h-Getting_started"></span>Getting started<span data-mw-comment-end="h-Getting_started"></span></h2><!--__DTELLIPSISBUTTON__{"threadItem":{"headingLevel":2,"name":"h-","type":"heading","level":0,"id":"h-Getting_started","replies":["h-General_principles-Getting_started","h-Ballpark_numbers-Getting_started","h-Percentiles-Getting_started","h-Latency-Getting_started"]}}--></div> <p>These guidelines focus both on individually <b>perceived</b> <b>performance</b> (your code runs relatively fast) and <b>scalability</b> (your code can run fast, even on large wikis and when run many times concurrently). </p> <div class="mw-heading mw-heading3"><h3 id="General_principles" data-mw-thread-id="h-General_principles-Getting_started"><span data-mw-comment-start="" id="h-General_principles-Getting_started"></span>General principles<span data-mw-comment-end="h-General_principles-Getting_started"></span></h3></div> <ol><li><b>Identify hot spots</b>. Which parts of your code take the most time to execute? These may deserve extra care during code review in the future. The team should maintain awareness of these hot spots by regularly (<i>e.g.</i> monthly) verifying and measuring your code's performance in production. When making choices in the interest of performance, measure instead of relying on intuition. Results can be surprising and may change over time. See also: <a href="/wiki/MediaWiki_Engineering/Guides/Measure_backend_performance" title="MediaWiki Engineering/Guides/Measure backend performance">How to measure backend performance</a>.</li> <li><b>Identify relevant latency metrics</b>, and take responsibility for them. As developers you know best how your code is exercised and what high-level actions are meaning to your product. Monitoring their latency will let you know when an experience becomes slower and/or when there are capacity problems. Consider documenting your latency metrics, and review them on a monthly or quarterly basis to prioritise maintenance as-needed to uphold the quality of service. <a href="/wiki/Performance/Metrics#Save_Timing" title="Performance/Metrics">Example: Save Timing documentation</a>.</li> <li><b>Treat the infrastructure as one that we share with others</b>. Your code can become a high-traffic workload in Wikimedia production! Each web request has a strict <a href="/wiki/MediaWiki_at_WMF#Timeouts" title="MediaWiki at WMF">HTTP timeout</a>. SQL query may be automatically stopped after 30s. Perform long-running tasks on a dedicated server instead, <i>e.g.</i> via the JobQueue. See also <a href="#Long-running_queries">#Long-running queries</a>.</li> <li><b>Tune your database queries</b>. Wikimedia heavily depends on its caching layers to survive. However, our caching exists primarily to increase capacity, not speed. Hitting Memcached first means we need fewer database servers. Remember that a cached result eventually has to be refreshed. A slow database query behind Memcached, is still a slow query! See also <a href="#Design_for_cache_miss">#Design for cache miss</a></li> <li><b>Choose the right persistence layer</b> for your needs. Only cache data if your code can also performantly respond when the cached data is missing; otherwise, use stronger persistence for your data. See also <a href="#Persistence_layer">#Persistence layer</a>. When designing tables, or adding new database queries, consider that every query must use an index (including write queries!). See also: <a href="#Index">#Index</a>.</li> <li><b>The cache hit ratio should be as high as possible</b>. When you introduce new batching calls or combined requests, underlying requests should retain effective cache use. Highly variable URLs or data that is cached under multiple permutations of cache keys, tend to worsen cache-hit ratio. See <a href="#Leverage_the_platform!">#Leverage the platform!</a> for platform capabilities that also enable high cache-hit ratios.</li> <li><b>Subpar performance can be indicative of a deeper issue</b> in how the code is solving a particular need. Think about the root cause, and whether certain costs can be structurally avoided or deferred. See also: <a href="#You_are_not_alone">#You are not alone</a>.</li> <li><b>Avoid cookies whenever possible</b>. Cookies create significant risks for CDN stability, and add unnecessary data transfers for clients. Consider using sessionStorage or localStorage (via mw.storage) as an alternative to cookies. Before adding cookies, consult with the MediaWiki Platform Team or SRE Traffic. See also <a href="#Cookies">#Cookies</a>.</li></ol> <div class="mw-heading mw-heading3"><h3 id="Ballpark_numbers" data-mw-thread-id="h-Ballpark_numbers-Getting_started"><span data-mw-comment-start="" id="h-Ballpark_numbers-Getting_started"></span>Ballpark numbers<span data-mw-comment-end="h-Ballpark_numbers-Getting_started"></span></h3></div> <ul><li>When accessing information (<i>e.g.</i> a view or API request over GET), aim for your backend to <b>respond within 50ms at the median</b> and within 200ms at the p99. In other words, common requests to popular data that benefit from internally warm caches respond within 50ms (<i>e.g.</i> database server cache, or Memcached/Apcu), and requests that encounter internal cache misses still gather and render all data within 200ms.</li> <li>When performing <b>write actions, respond within 500ms</b> at the p99 (<i>e.g.</i> a POST request). Make sure that the amount of work a request performs is naturally and deterministically limited (<i>e.g.</i> do not rely on server-level memory limits or timeouts). It is encouraged to deny and incentivize against usage patterns that we cannot maintain at scale. Remember that you can schedule tasks via the <a href="https://www.mediawiki.org/wiki/Manual:Job_queue/For_developers" class="extiw" title="mw:Manual:Job queue/For developers">Job queue</a> and <a href="https://www.mediawiki.org/wiki/Deferred_updates" class="extiw" title="mw:Deferred updates">Deferred updates</a> which let you run code asynchronously either on the same server after the response has been sent ("post-send deferred update"), or a few seconds later in a separate cluster ("jobqueue job").</li></ul> <div class="mw-heading mw-heading3"><h3 id="Percentiles" data-mw-thread-id="h-Percentiles-Getting_started"><span data-mw-comment-start="" id="h-Percentiles-Getting_started"></span>Percentiles<span data-mw-comment-end="h-Percentiles-Getting_started"></span></h3></div> <p>For backend code, <b>look at high percentiles</b> (such as the p95th and 99th percentile) instead of mean averages or medians (learn why: <sup id="cite_ref-1" class="reference"><a href="#cite_note-1"><span class="cite-bracket">[</span>1<span class="cite-bracket">]</span></a></sup><sup id="cite_ref-2" class="reference"><a href="#cite_note-2"><span class="cite-bracket">[</span>2<span class="cite-bracket">]</span></a></sup><sup id="cite_ref-3" class="reference"><a href="#cite_note-3"><span class="cite-bracket">[</span>3<span class="cite-bracket">]</span></a></sup>). Backend latencies tend to vary based on factors like cache warmth and server load (instead of varying based on what a person is doing), which means even an issue with 1% of requests may affect everyone on a regular basis (though perhaps not at the same time). Monitoring the average would systematically ignore well over half the audience. Monitoring the 99th percentile tells you how your code behaves when it matters most. </p><p>Performance data often hides two different stories. One for users accessing the application on a warm cache, and another with a cold cache. Calculating averages on such a dataset is deceptive. <b>Benchmark with at least 10,000 iterations</b> to calculate a 50th and 90th percentile. If these numbers differ greatly, that would indicate a performance problem. For example, if the code requires network fetches and you have many resources to fetch, there may be a group that leverages cached resources (thus avoiding network roundtrips) and a group that does not. A rule of thumb about statistical significance in performance context is that one needs 10,000 data points to calculate a 90th percentile; and 100,000 for a 99th percentile; and 1 million for a 99.9th. </p> <div class="mw-heading mw-heading3"><h3 id="Latency" data-mw-thread-id="h-Latency-Getting_started"><span data-mw-comment-start="" id="h-Latency-Getting_started"></span>Latency<span data-mw-comment-end="h-Latency-Getting_started"></span></h3></div> <p>Aim for your software to provide a reasonably fast experience, regardless of network latency. Client-side latency depends on numerous factors, including: your backend response time, the CDN response time, the <a href="https://en.wikipedia.org/wiki/Round-trip_delay_time" class="extiw" title="en:Round-trip delay time">round-trip time</a> from the client device to our servers and back (RTT), and the transfer rate (bandwidth) that the client's network is capable of. </p><p>The RTT and bandwidth are not always related. For example, a Gigabit connection with an RTT of 2 seconds will not transfer anything in less than 2 seconds, regardless of payload size. You can think of bandwidth as the size of a truck on a highway (or the number of highway lanes), and RTT is how fast one may travel. If 1000 kg are transferred by the truck in 1 hour, this does <i>not</i> mean that 1 kg will arrive in 3 seconds. We recommend the free ebook <a rel="nofollow" class="external text" href="https://hpbn.co/"><i>High Performance Browser Networking</i></a> by Ilya Grigorik. In particular, mobile devices take more time to reactivate their network connection after a period of inactivity. </p><p>Strategies for reducing or masking latency: </p> <ul><li><b>Responses to unregistered users should be cacheable</b> by the CDN (<i>i.e.</i> requests from browsers without an active login or edit session). <ul><li>For page views, page actions, and special pages, this is controlled by OutputPage.</li> <li>A good example of a cacheable <a href="https://www.mediawiki.org/wiki/Action_API" class="extiw" title="mw:Action API">Action API</a> module is ApiOpenSearch.</li> <li>A good example of a cacheable <a href="https://www.mediawiki.org/wiki/API:REST_API" class="extiw" title="mw:API:REST API">REST API</a> route is Rest\Handler\SearchHandler.</li></ul></li> <li><b>Split high-level operations into smaller re-usable methods</b>. This allows different parts of your code to only ask for and compute what is needed, instead of accumulating ever more computations that "always" happen whenever anything interacts with your feature (which would mean no high-level actions are optimal, not even the high-traffic ones).</li></ul> <ul><li><b>Set a tight timeout</b>. Once you've set a p99 latency objective for your backend responses, think about what might cause a response to fall in that last 1%. For example, if your feature includes internal requests to other services, what timeouts do they have? What retries do they allow? Consider what happens if suddenly a majority of web traffic starts to exercise your 1% scenario. Do we start to exhaust all backend capacity? Or do we fail quickly and free up resources so that other parts of the site stay up? Once you've set and achieved a latency objective, put a limit in place to shift outliers from "slow" toward "error". This way callers can quickly re-try, which some services may even do automatically.</li></ul> <p>See also <a href="/wiki/MediaWiki_Engineering/Guides/Frontend_performance_practices#Latency" title="MediaWiki Engineering/Guides/Frontend performance practices">Page load performance#Latency</a> for advice on preloading and stale revalidation. </p><p><a href="#Leverage_the_platform!">Leverage the platform</a> and benefit by building atop prior work for scaling MediaWiki to our performance needs </p> <div style="clear: both; margin-bottom: .5em; float: none; width: auto;" class="toclimit-3"><meta property="mw:PageProp/toc"/></div> <div class="mw-heading mw-heading2 ext-discussiontools-init-section"><h2 id="How_often_will_my_code_run?" data-mw-thread-id="h-How_often_will_my_code_run?"><span id="How_often_will_my_code_run.3F"></span><span data-mw-comment-start="" id="h-How_often_will_my_code_run?"></span>How often will my code run?<span data-mw-comment-end="h-How_often_will_my_code_run?"></span></h2><!--__DTELLIPSISBUTTON__{"threadItem":{"headingLevel":2,"name":"h-","type":"heading","level":0,"id":"h-How_often_will_my_code_run?","replies":["h-Caching_layers-How_often_will_my_code_run?","h-Cookies-How_often_will_my_code_run?","h-Design_for_cache_miss-How_often_will_my_code_run?","h-Practical_examples_about_caching_layers-How_often_will_my_code_run?"]}}--></div> <p>Think about how often the server will execute your code. We tend to categorise server-side concerns in these buckets: </p> <ol><li><i>Always</i>. <ul><li>Critical code running unconditionally on every backend request. This should be kept minimal with clearly low cost. This includes early Setup hooks, extension.json callbacks, and service wiring.</li> <li>The typical budget for new needs from core or extension hooks during the Setup phase is <b>0.1ms</b>.</li></ul></li> <li><i>On page views</i>. <ul><li>Code running on all HTML web responses. This is almost always, but not for action=raw requests, or HTTP 304 "Not Modified" responses to a page view URL.</li> <li>The typical budget for new logic on a page view is <b>1ms</b>. We aim to respond within 50ms to a majority of requests (<a href="#Ballpark_numbers">#Ballpark numbers</a>).</li></ul></li> <li><i>When parsing page content</i>. <ul><li>Most backend requests for page views fetch page content from the ParserCache and render a skinned page view. The parsing of page content happens on a relatively small portion of page views only (<i>e.g.</i> cache miss), as well as during the response to saving of edits. For that reason, it is acceptable to perform a limited number of more expensive operations during the parser run, <i>e.g.</i> from a parser hook. These hooks generally do not execute while someone is waiting during a page view, as the result of these operations is retained in the parser cache.</li> <li>The typical budget for components that render content is <b>10ms</b> (for most pages using your feature), or up to 100ms for pages that use your feature in an unusually complex manner.</li></ul></li> <li><i>When performing a write action</i>. <ul><li>Editing is the most common non-read action. The latency of processing an edit is actively monitored as <a href="/wiki/Performance/Metrics#Save_Timing" title="Performance/Metrics">Save Timing</a>. <ul><li>The typical budget for components that parse content or otherwise synchronously hook into edit submissions is <b>10ms</b> (for most pages), or up to 100ms for unusually complex pages. We aim to respond within 1 second for write actions, and there are many extensions participating in that shared budget (<a href="#Ballpark_numbers">#Ballpark numbers</a>).</li></ul></li></ul></li></ol> <div class="mw-heading mw-heading3"><h3 id="Caching_layers" data-mw-thread-id="h-Caching_layers-How_often_will_my_code_run?"><span data-mw-comment-start="" id="h-Caching_layers-How_often_will_my_code_run?"></span>Caching layers<span data-mw-comment-end="h-Caching_layers-How_often_will_my_code_run?"></span></h3></div> <p>Caching layers to be aware of: </p> <ol><li><b>Browser caches.</b> In addition to the browser's HTTP cache, we also cache JavaScript and CSS modules in HTML5 LocalStorage which acts as a defragmented browser cache that significantly reduces network requests (details at <a href="https://www.mediawiki.org/wiki/ResourceLoader/Architecture#Store" class="extiw" title="mw:ResourceLoader/Architecture">ResourceLoader/Architecture#Store</a>).</li> <li><b>CDN cache</b> (aka "Edge caching" or "Varnish frontend"). The Varnish caches stores <i>entire HTTP responses</i>, including thumbnails of images, frequently-requested article content, ResourceLoader modules, and most anything else that can be retrieved by URL. Wikimedia operates CDN front-ends in <a href="/wiki/Data_centers" title="Data centers">multiple data centers</a> around the world to reduce latency between browser and server. See <a href="/wiki/MediaWiki_at_WMF" title="MediaWiki at WMF">MediaWiki at WMF</a> for some details.</li> <li><b>Object cache</b>. Our object caching layers scale from small ephemeral memory on individual web servers (<i>e.g.</i> PHP-APCU, which is uncoordinated and unreplicated), to large Memcached clusters, and even multi-DC replicated clusters backed by SQL databases (<i>e.g.</i> ParserCache). To learn which object cache to use, refer to <a href="https://www.mediawiki.org/wiki/Object_cache" class="extiw" title="mw:Object cache">Object cache</a> on mediawiki.org. To learn more more about the principles and expectations of Memcached, refer to <a href="/wiki/Memcached_for_MediaWiki" title="Memcached for MediaWiki">Memcached at WMF</a>.</li> <li><b>MySQL</b>. Database servers have their own internal query caches and buffer pools. This works transparently and is not directly controllable. It means that repeat queries for the same data are generally faster even with no other caching logic written by you.</li></ol> <p>Think about how you will invalidate or expire content from the various caching layers. Is it by purging a specific cache key? Or by changing part of a URL or cache key such that the old cache is naturally not used? Your application needs will determine your cache purging strategy. </p><p>Since the CDN cache serves content by URL, URLs ought to be deterministic -- that is, they should not serve different content from the same URL. Different content belongs at a different URL. This should be true especially for anonymous users. </p> <div class="mw-heading mw-heading3"><h3 id="Cookies" data-mw-thread-id="h-Cookies-How_often_will_my_code_run?"><span data-mw-comment-start="" id="h-Cookies-How_often_will_my_code_run?"></span>Cookies<span data-mw-comment-end="h-Cookies-How_often_will_my_code_run?"></span></h3></div> <p>Avoid cookies whenever possible. Cookies create significant risks for CDN stability (per <a href="#Caching_layers">#Caching layers</a> above), and increase the payload of all web requests creating <b>unnecessary data transfers</b> for clients. <b>Consider using <code>sessionStorage</code></b> or <code>localStorage</code> (via <a class="external text" href="https://doc.wikimedia.org/mediawiki-core/master/js/module-mediawiki.storage.html">mw.storage</a>) as an alternative to cookies. Before adding cookies, consult with the MediaWiki Platform Team or SRE Traffic. </p> <div class="mw-heading mw-heading3"><h3 id="Design_for_cache_miss" data-mw-thread-id="h-Design_for_cache_miss-How_often_will_my_code_run?"><span data-mw-comment-start="" id="h-Design_for_cache_miss-How_often_will_my_code_run?"></span>Design for cache miss<span data-mw-comment-end="h-Design_for_cache_miss-How_often_will_my_code_run?"></span></h3></div> <dl><dd><i>Main article: <a href="/wiki/Memcached_for_MediaWiki#WANObjectCache" title="Memcached for MediaWiki">Memcached for MediaWiki#WANObjectCache</a></i></dd></dl> <p><b>Cache-on-demand</b>: MediaWiki generally promotes a cache-on-demand strategy, formally known as a "<b>cache aside</b>" strategy. When your code creates or modifies something after a user hits "save" or "submit", then along with saving the new data to your primary database, you must also invalidate the relevant caches. (See "<a href="#Caching_layers">#Caching layers</a>" for more.) </p><p>If there is a many-to-one relationship between a piece of data and the cache keys that may contain a copy of that data, or if the cache keys contain variables that you cannot easily know during a change event, then you can leverage WANObjectCache <code>getWithSetCallback</code> with a shared "check" key passed in. The check key can be "bumped" via <code>touchCheckKey</code> to perform invalidations <i>instead</i> of using <code>delete()</code>. This also avoids cache stampedes for hot keys, because when a specific key has a related check key that is touched/newer than the stored data, the stored data is still there. This allows WANObjectCache to automatically use the stale value for a short period of time if the site is under heavy load. </p><p><b>Cache misses are normal</b>: Avoid writing code that, on cache miss, is very slow. For instance, a slow <code>COUNT(*)</code> database cached behind a fast Memcached key is still the same slow database query; cache misses and timeouts eat a lot of resources. Our web servers and database servers explicitly stop (most kinds of) requests if they <a href="/wiki/MediaWiki_at_WMF#Timeouts" title="MediaWiki at WMF">exceed a 60 seconds timeout</a>. There is no exemption for presumed-rare requests where your code is filling its caches with slow queries. </p><p>Write your queries such that an uncached computation will still meet your latency budget (see <a href="#Ballpark_numbers">#Ballpark numbers</a>). </p><p>If you can't make it fast, see if you can do it in the background. For example, MediaWiki core's ParserCache is for the most part not cache-on-demand, but rather pre-populated during edits. It also uses a database as its storage backend rather than Memcached. This has the benefit of almost never exposing a reader to a cache miss, however has the downside of placing a significant responsibility on application code to handle stale values. In the case of ParserCache, it inspects and "verifies" the ParserOutput values after retrieval from cache, against on the current revision ID, thus not relying on purges or replication, and thus being immune to race conditions between web requests writing to the same cache key. </p><p>Another example is the "Statistics" special page, which runs expensive queries offline via a maintenance script and stores its results in a database table, which acts as a non-expiring cache. </p><p><b>Watch out for cached HTML</b>: HTML output may be cached for a long time and still needs to be supported by the CSS and JS. Problems where old JS/CSS hang around are in some ways easier to test, but stale HTML can be insidious! </p><p><b>Cache-on-save</b>: Prior to 2014, it was not uncommon for certain MediaWiki features to try to keep the cache in sync during writes to the database, by proactively recomputing new cache values. This practice is no longer supported as it is unreliable at our scale for numerous reasons, including: </p> <ul><li>It wrongly assumes database writes never fail, time out, or rolled back later in the same request. It is harmless to do $db->insert() followed by $cache->purge(), because even if your database transaction later fails to commit or times out, the cache will simply repopulate. However, writing to the cache at this point would potentially cause unsaved data to stay in your cache for a long time, confusing subsequent requests.</li> <li>It wrongly assumes there are no concurrent web requests that change the same database rows or cache keys. When two requests come in that edit the same information, MySQL allows them to work in parallel and later ensures the transactions are committed in a deterministic order. However, there is no guarantee that both requests take up the same amount of time and that the "first" one to win the database write, also reaches the cache first. Thus you may end up writing "edit 1" then "edit 2" to the database, but write "edit 2" and overwrite with "edit 1" in the cache, and it stays that way.</li> <li>It wrongly assumes there is no replication lag. Chances are that if your code relies heavily on cache data reflecting the primary database state instantly, it probably does not handle well when there is a cache-miss and you instead read data from a replica database. There is of course no "instantly" anything, unless one would lock all requests together.</li> <li>It wrongly assumes caches are always up and writable. For primary databases, we trade speed for high availability and replication of data (each added host replicates the same data) and thus we accept the (small) risk of failing a web request if the DBs are down. For caches, we focus almost exclusively on speed and maximising potential capacity (each added host is a shard with its own portion of the data, not stored elsewhere). We cannot take the site down every time a cache is slow, unresponsive, or undergoing maintenance; thus cache writes can and will fail in ways we ignore. The only guarantee is that cache attempts are fast (EmptyBagOStuff is a valid implementation of BagOStuff!).</li> <li>It wrongly assumes the application runs from a single region or data center, with cache writes eventually "seen" by all subsequent requests. It is vital that each data center is (mostly) independent, which a requirement to write data to another DC's cache would violate. Each data center is responsible for lazily populating its own caches. Only <a href="https://www.mediawiki.org/wiki/Object_cache#Services" class="extiw" title="mw:Object cache">purges are broadcasted</a>. See also <a href="/wiki/Performance/Multi-DC_MediaWiki" title="Performance/Multi-DC MediaWiki">Multi-DC MediaWiki</a>, <a class="external text" href="https://techblog.wikimedia.org/2019/02/14/perf-matters-at-wikipedia-in-2015/">Perf Matters 2015: Hello WANObjectCache</a>, and <a class="external text" href="https://techblog.wikimedia.org/2022/12/08/perf-matters-at-wikipedia-2016/#one-step-closer-to-multi-dc">Perf Matters 2016: One step closer</a>.</li></ul> <div class="mw-heading mw-heading3"><h3 id="Practical_examples_about_caching_layers" data-mw-thread-id="h-Practical_examples_about_caching_layers-How_often_will_my_code_run?"><span data-mw-comment-start="" id="h-Practical_examples_about_caching_layers-How_often_will_my_code_run?"></span>Practical examples about caching layers<span data-mw-comment-end="h-Practical_examples_about_caching_layers-How_often_will_my_code_run?"></span></h3></div> <ul><li>The WikidataClient extension was <b>fetching a large object from memcached</b> containing the complete metadata for all wikis, when it needed information about 1 wiki. This led to significant network congestion. This was first reduced by moving the cache from Memcached to php-apcu. <a href="https://gerrit.wikimedia.org/r/93773" class="extiw" title="gerrit:93773">Change 93773</a> later solved it by instead storing the precomputed information as a configuration variable, thus removing the need for any computations at runtime. Background at <a href="https://phabricator.wikimedia.org/T58602" class="extiw" title="phab:T58602">T58602</a>.</li> <li>The GuidedTour extension was performing <b>uncached template parses</b> on every page view. This led to significant database load due to not taking advantage of ParserCache. (<a href="https://gerrit.wikimedia.org/r/c/mediawiki/extensions/GuidedTour/%2B/67230" class="extiw" title="gerrit:c/mediawiki/extensions/GuidedTour/+/67230">change 67230</a>)</li> <li>The GettingStarted extension <b>introduced a new cookie</b> containing the word <code>Token</code>, which matches a regular expression that Varnish CDN recognises as indicating logged-in page views to <i>never</i> cache. See <a href="https://phabricator.wikimedia.org/rEGST22fbeedfc51fdd3ed780ed52bc880df8d07bfc19" class="extiw" title="phab:rEGST22fbeedfc51fdd3ed780ed52bc880df8d07bfc19">the code</a>, <a href="https://gerrit.wikimedia.org/r/130228" class="extiw" title="gerrit:130228">an initial revert</a>, <a href="https://gerrit.wikimedia.org/r/130229" class="extiw" title="gerrit:130229">another early fix</a>, <a href="https://gerrit.wikimedia.org/r/130332" class="extiw" title="gerrit:130332">another revert commit</a>, <a href="https://gerrit.wikimedia.org/r/130336" class="extiw" title="gerrit:130336">the Varnish layer workaround</a>, <a href="https://gerrit.wikimedia.org/r/130352" class="extiw" title="gerrit:130352">the followup fix</a>, the GettingStarted fix <a href="https://gerrit.wikimedia.org/r/130381" class="extiw" title="gerrit:130381">part 1</a> and <a href="https://gerrit.wikimedia.org/r/130393" class="extiw" title="gerrit:130393">part 2</a>, and <a href="https://gerrit.wikimedia.org/r/130516" class="extiw" title="gerrit:130516">the regex fix</a>.</li> <li>The <a href="https://www.mediawiki.org/wiki/Extension:TwnMainPage" class="extiw" title="mw:Extension:TwnMainPage">TwnMainPage extension</a> <b>offloads expensive statistics to the JobQueue</b>. The extension periodically queues a job to recalculate statistics shortly before the cache would expire. In the event that the cache data disappears, it temporarily shows no statistics until the job is completed. This a good compromise to keep pages fast and avoid slow queries during a cache miss. See <a rel="nofollow" class="external text" href="https://github.com/wikimedia/mediawiki-extensions-TwnMainPage/blob/master/CachedStat.php">CachedStat.php</a>. It sets a limit of 1 second for calculating stats in <a rel="nofollow" class="external text" href="https://github.com/wikimedia/mediawiki-extensions-TwnMainPage/blob/master/specials/SpecialTwnMainPage.php#L227">SpecialTwnMainPage.php</a>.</li> <li>A change to the RecentChanges feed in MediaWiki core <b>accidentally removed Cache-Control</b>. The feed was previously cached for only 10 seconds by the CDN, but that was enough to absorb the vast majority of feed crawling. The cache was promptly restored (<a href="https://phabricator.wikimedia.org/T65249" class="extiw" title="phab:T65249">T65249</a>).</li></ul> <div class="mw-heading mw-heading2 ext-discussiontools-init-section"><h2 id="Leverage_the_platform!" data-mw-thread-id="h-Leverage_the_platform!"><span id="Leverage_the_platform.21"></span><span data-mw-comment-start="" id="h-Leverage_the_platform!"></span>Leverage the platform!<span data-mw-comment-end="h-Leverage_the_platform!"></span></h2><!--__DTELLIPSISBUTTON__{"threadItem":{"headingLevel":2,"name":"h-","type":"heading","level":0,"id":"h-Leverage_the_platform!","replies":[]}}--></div> <ul><li>Move work to post-send <a href="https://www.mediawiki.org/wiki/Deferred_updates" class="extiw" title="mw:Deferred updates">Deferred updates</a> or <a href="https://www.mediawiki.org/wiki/Manual:Job_queue/For_developers" class="extiw" title="mw:Manual:Job queue/For developers">Jobs</a> if it doesn't have to happen in the critical path of a web response.</li> <li>When interacting with Memcached, use the <code>getWithSet</code> idiom and use <a class="external text" href="https://doc.wikimedia.org/mediawiki-core/master/php/classWANObjectCache.html#details">WANObjectCache</a>. Avoid calling Memcached directly. WANCache automatically takes care of numerous "at scale" needs such as stampede protection, purging, mutex locks, and warms your caches by automatically regenerating values before they expire. This reduces the chances of hot keys ever getting a cache miss. WANCache also integrates with dev tooling such as rate and latency stats on <a class="external text" href="https://grafana.wikimedia.org/d/lqE4lcGWz/wanobjectcache-key-group?orgId=1&amp;var-kClass=gadgets_definition">Grafana: WANObjectCache</a>.</li> <li>Use core service classes whenever possible and look for batch methods that can process several of your requests in parallel. Our database abstraction layer, cache interfaces, and HTTP clients all support batching. For example: <code>IDatabase::select</code>, <code>IDatabase::selectFieldValues</code>, <code>BagOStuff::getMulti</code>, <code>HttpRequestFactory::createMultiClient</code>, and <code>FileBackend::doOperations</code>.</li></ul> <div class="mw-heading mw-heading2 ext-discussiontools-init-section"><h2 id="You_are_not_alone" data-mw-thread-id="h-You_are_not_alone"><span data-mw-comment-start="" id="h-You_are_not_alone"></span>You are not alone<span data-mw-comment-end="h-You_are_not_alone"></span></h2><!--__DTELLIPSISBUTTON__{"threadItem":{"headingLevel":2,"name":"h-","type":"heading","level":0,"id":"h-You_are_not_alone","replies":[]}}--></div> <p>If your code is amplifying a pre-existing performance issue in another component or service, identify these and ensure relevant teams are made aware. The MediaWiki Platform Team can help you in finding and/or advocating for these cross-component issues. </p><p>Work with the team to understand general performance targets before you start architecting your system. For example, a user-facing application might have an acceptable latency of 200ms while a database might have something like 20ms or less, especially if further access is decided based on the results of previous queries. You don't want to prematurely optimise, but you need to understand if your targets are physically possible. </p><p>You might not need to design your own backend; consider using an existing service, or having someone design an interface for you. Consider modularization. Low-level performance is hard, avoid reinventing the wheel! </p> <div class="mw-heading mw-heading2 ext-discussiontools-init-section"><h2 id="Persistence_layer" data-mw-thread-id="h-Persistence_layer"><span data-mw-comment-start="" id="h-Persistence_layer"></span>Persistence layer<span data-mw-comment-end="h-Persistence_layer"></span></h2><!--__DTELLIPSISBUTTON__{"threadItem":{"headingLevel":2,"name":"h-","type":"heading","level":0,"id":"h-Persistence_layer","replies":["h-Schema_and_API_design-Persistence_layer","h-Practical_examples_of_persistence-Persistence_layer","h-Indexing-Persistence_layer","h-Multiple_datacenters-Persistence_layer"]}}--></div> <p>Choose the right persistence layer for your needs. In some cases, a cache can be used instead of a persistence layer. </p><p>MediaWiki is configured in production to leverage local services that include Memcached, JobQueue, ExternalStore and MainStash (MariaDB), and Swift. These reside on a low-latency network in the same data center. </p><p>The most appropriate layer is the lightest store that meets your requirements. Avoid incurring overhead that serves unneeded guarantees. Here's when we recommend each: </p> <ol><li>Cache - short-term storage of ephemeral unstructured data of small or medium size (up to 800KB).</li> <li>MainStash - replicated storage of ephemeral unstructured data and small to large blobs (avoid larger than 2MB), backed by MySQL/MariaDB.</li> <li>JobQueue - reliable short-term persistence of a job description. Once the job is completed, the job parameter data is automatically pruned.</li> <li>Database - long-term storage of structured data and blobs, backed by MySQL/MariaDB via dedicated table schemas.</li> <li>FileRepo - long-term storage of binary media files, backed by Swift. This is used by user file uploads, but can also be used for generated files like the <a href="https://www.mediawiki.org/wiki/Extension:Score" class="extiw" title="mw:Extension:Score">Score extension</a> (for MIDI files) and <a href="https://www.mediawiki.org/wiki/Extension:ConfirmEdit" class="extiw" title="mw:Extension:ConfirmEdit">ConfirmEdit extension</a> (for captchas). See <a href="/wiki/Media_storage" title="Media storage">wikitech:Media storage</a> for details.</li></ol> <p>Most caches are backed by Memcached, see <a href="https://www.mediawiki.org/wiki/Object_cache" class="extiw" title="mw:Object cache">Object cache</a> on mediawiki.org for guidance of when and how to WANObjectCache and the other cache interfaces. Use a cache for data that may persist between web requests, but that is okay to quietly fail and be pruned at any time. Use memcached to store objects if the database <i>could</i> recreate them but it would be computationally expensive to do so — you don't want to recreate them too often. You can imagine a spectrum between caches and stores, varying on how long developers expect objects to live in the service before getting evicted; refer to <a href="#Caching_layers">#Caching layers</a> for more details. </p> <div class="mw-heading mw-heading3"><h3 id="Schema_and_API_design" data-mw-thread-id="h-Schema_and_API_design-Persistence_layer"><span data-mw-comment-start="" id="h-Schema_and_API_design-Persistence_layer"></span>Schema and API design<span data-mw-comment-end="h-Schema_and_API_design-Persistence_layer"></span></h3></div> <ul><li><b>Store files and metadata under stable identifiers</b>, <i>e.g.</i> revision IDs, page IDs, user IDs won't change; whereas page titles and user names do. Nearly all <b>tables should have an immutable "ID" column</b>, especially if there is also a "name" column. ID columns should be unsigned integers with values assigned by the database (<i>e.g.</i> AUTOINCREMENT). In rare cases, a UUID column can be used instead. ID columns are essential for efficient indexes, low-maintenance foreign keys, and simpler schema migration scripts.</li> <li><b>Large immutable blobs typically go in ExternalStore</b>. We must avoid large and numerous blob inserts on the main database clusters (<i>e.g.</i> s1-s8) as this can lead to contention and poor performance for many reasons (<i>e.g.</i> replication lag, poor use of buffer pool, poor use of disk space). This is why we store revision content (<i>e.g.</i> wikitext) in ExternalStore, which we use an immutable append-only key-value database. If the blobs are ephemeral, use a <a class="mw-selflink-fragment" href="#Persistence_layer">cache layer</a> instead such as Memcached or MainStashDB.</li> <li><b>Try to design your schema and API around single-record access for create/update/delete and multi-record access for fetching/querying</b>. If the internal classes or web API needs to support multi-record write operations, then a loop will often suffice. If hundreds of records are often involved or if the entry point becomes high traffic, then batched write query optimisations become justified. Most batching optimisations should be focused on APIs that can fetch multiple records by ID/name or page through records by ID/name/timestamp (possibly involving a JOIN with other records).</li></ul> <div class="mw-heading mw-heading3"><h3 id="Practical_examples_of_persistence" data-mw-thread-id="h-Practical_examples_of_persistence-Persistence_layer"><span data-mw-comment-start="" id="h-Practical_examples_of_persistence-Persistence_layer"></span>Practical examples of persistence<span data-mw-comment-end="h-Practical_examples_of_persistence-Persistence_layer"></span></h3></div> <div class="mw-heading mw-heading4"><h4 id="Example:_Permanent_names" data-mw-thread-id="h-Example:_Permanent_names-Practical_examples_of_persistence"><span data-mw-comment-start="" id="h-Example:_Permanent_names-Practical_examples_of_persistence"></span>Example: Permanent names<span data-mw-comment-end="h-Example:_Permanent_names-Practical_examples_of_persistence"></span></h4></div> <p>MediaWiki stores metadata about uploaded files in the database, using the display title as identifier. This requires a slow database operation when someone renames an uploaded file title, with updates to several database tables, and updates to a potentially large number of rows (every matching and associated table row). With a stable numerical identifier,  title changes can be applied to the original row only. All other data remains associated by the stable ID.<sup id="cite_ref-4" class="reference"><a href="#cite_note-4"><span class="cite-bracket">[</span>4<span class="cite-bracket">]</span></a></sup> </p> <div class="mw-heading mw-heading4"><h4 id="Example:_Large_object_size" data-mw-thread-id="h-Example:_Large_object_size-Practical_examples_of_persistence"><span data-mw-comment-start="" id="h-Example:_Large_object_size-Practical_examples_of_persistence"></span>Example: Large object size<span data-mw-comment-end="h-Example:_Large_object_size-Practical_examples_of_persistence"></span></h4></div> <p>Avoid storing multi-megabyte data in Memcached, this is discarded automatically. Verify whether caching is effective in both hit ratio, saving time, and saving backend load. For example, prior to 2015, ResourceLoader stored minified JavaScript code in Memcached, while the cache had a high hit-ratio and saved redundant minification computations, we proved that it was faster to run the minifier than to fetch and wait for cached data over the network from a Memcached server. It was cheaper and faster to recalculate than to retrieve, and doing so freed up web server load, Memcached load, and network traffic. Another example is the Watchlist data, which performs a lookup like "is this page watched by current user", which we choose not to cache, because it's indexed and just as fast to query from a database as from Memcached, and more importantly, caching it would make things worse because the wide range of page-user combinations means it's almost never a cache-hit and thus you then wait for both Memcached, and then after that also wait for a database server still. <a class="external text" href="https://grafana.wikimedia.org/d/lqE4lcGWz/wanobjectcache-key-group?orgId=1">WANObjectCache in Grafana</a> provides statistics about how effective your cache keys are in cache-hit ratio, fetch time, and computation time. </p> <div class="mw-heading mw-heading4"><h4 id="Example:_Job_queue_use_cases" data-mw-thread-id="h-Example:_Job_queue_use_cases-Practical_examples_of_persistence"><span data-mw-comment-start="" id="h-Example:_Job_queue_use_cases-Practical_examples_of_persistence"></span>Example: Job queue use cases<span data-mw-comment-end="h-Example:_Job_queue_use_cases-Practical_examples_of_persistence"></span></h4></div> <ul><li>The <a href="https://www.mediawiki.org/wiki/Extension:TimedMediaHandler" class="extiw" title="mw:Extension:TimedMediaHandler">TimedMediaHandler</a> extension uses jobs to transcode large video uploads into variants for specific resolutions and <a href="https://en.wikipedia.org/wiki/Video_codec" class="extiw" title="en:Video codec">video codecs</a>.</li> <li>The <a href="https://www.mediawiki.org/wiki/Extension:UploadWizard" class="extiw" title="mw:Extension:UploadWizard">UploadWizard</a> extension uses an Action API to upload large files in chunks. The API queues a job to take care of reassembling the chunks into a single file for downloading and streaming. The uploader can meanwhile start writing the file description, metadata, <i>etc.</i>, while the UploadWizard frontend uploads the file one chunk at a time in the background.</li> <li>The core <code>HTMLCacheUpdate</code> job purges articles from the CDN (Varnish) after a given template was edited. It also queues a job to invalidate the <code>page_touched</code> field of matching rows the MySQL database, which instructs ParserCache to re-parser the article next time someone views it.</li> <li>Other extensions that use the job queue include <a href="https://www.mediawiki.org/wiki/Extension:Renameuser" class="extiw" title="mw:Extension:Renameuser">RenameUser</a>, TranslationNotification, Translate, GWToolset, and MassMessage.</li></ul> <div class="mw-heading mw-heading4"><h4 id="Example:_Send_a_notification" data-mw-thread-id="h-Example:_Send_a_notification-Practical_examples_of_persistence"><span data-mw-comment-start="" id="h-Example:_Send_a_notification-Practical_examples_of_persistence"></span>Example: Send a notification<span data-mw-comment-end="h-Example:_Send_a_notification-Practical_examples_of_persistence"></span></h4></div> <p>Suppose we need to store a new notification. Good solution: run the actual notification action (sending an email) via the jobqueue. Bad solution: Send the email during the web request, thus delaying the browser response, and holding open the database transaction until the email is sent (and with no automatic re-try). </p> <div class="mw-heading mw-heading4"><h4 id="Example:_Count_active_users" data-mw-thread-id="h-Example:_Count_active_users-Practical_examples_of_persistence"><span data-mw-comment-start="" id="h-Example:_Count_active_users-Practical_examples_of_persistence"></span>Example: Count active users<span data-mw-comment-end="h-Example:_Count_active_users-Practical_examples_of_persistence"></span></h4></div> <p>The <a href="https://www.mediawiki.org/wiki/Extension:BetaFeatures" class="extiw" title="mw:Extension:BetaFeatures">Beta features extension</a> lets a user opt-in to a "Beta feature" and displays, to the user, how many users have opted in. The preference data is stored in the <code>user_properties</code> table. Counting the number of opted-in users every time the count is displayed would not have acceptable performance. Thus, the BetaFeatures extension for MediaWiki stores the count in a dedicated <code>betafeatures_user_counts</code> database table. It's important for a change in preference to immediately apply to a user's own experience on subsequent page views, but, it's not important to immediately increase or decrease the count. Therefore, BetaFeatures instead uses the job queue to recompute the count ever a preference change. Specifically, the extension queues a job that runs a SELECT query. This query can take a long time on large wikis - up to several minutes! Once done, the next time someone views the <a href="https://en.wikipedia.org/wiki/Special:Preferences#mw-prefsection-betafeatures" class="extiw" title="en:Special:Preferences">Beta features preferences</a> page, the new count is fetched from the "counts" table where the job stored it. Code: <a href="https://gerrit.wikimedia.org/r/plugins/gitiles/mediawiki/extensions/BetaFeatures/%2B/8996de9f6d6acc6d61cba3483338453c0794071b/includes/UpdateBetaFeatureUserCountsJob.php" class="extiw" title="gerrit:plugins/gitiles/mediawiki/extensions/BetaFeatures/+/8996de9f6d6acc6d61cba3483338453c0794071b/includes/UpdateBetaFeatureUserCountsJob.php">UpdateBetaFeatureUserCountsJob.php</a> and <a href="https://gerrit.wikimedia.org/r/plugins/gitiles/mediawiki/extensions/BetaFeatures/%2B/8996de9f6d6acc6d61cba3483338453c0794071b/includes/Hooks.php" class="extiw" title="gerrit:plugins/gitiles/mediawiki/extensions/BetaFeatures/+/8996de9f6d6acc6d61cba3483338453c0794071b/includes/Hooks.php">BetaFeaturesHooks.php</a>. </p> <div class="mw-heading mw-heading3"><h3 id="Indexing" data-mw-thread-id="h-Indexing-Persistence_layer"><span data-mw-comment-start="" id="h-Indexing-Persistence_layer"></span>Indexing<span data-mw-comment-end="h-Indexing-Persistence_layer"></span></h3></div> <p>When designing new tables, or adding new database queries, consider that all queries must use an index (including write queries!). Use <a rel="nofollow" class="external text" href="http://dev.mysql.com/doc/refman/4.1/en/using-explain.html">EXPLAIN</a> on your queries and create new indices where required. </p><p>Unless you're dealing with a tiny table, you need to index writes (similarly to reads). Watch out for deadlocks and for lock-wait timeouts. Try to do updates and deletes by primary query, rather than some secondary key. Try to avoid UPDATE/DELETE queries on rows that do not exist. Make sure join conditions are cleanly indexed. </p><p>You cannot index blobs, but you can index blob prefixes (the substring comprising the first several characters of the blob). </p><p>Compound keys - namespace-title pairs are all over the database. You need to order your query by asking for namespace first, then title! </p><p>Use EXPLAIN &amp; MYSQL DESCRIBE query to find out which indexes are affected by a specific query. If it says "Using temporary table" or "Using filesort" in the EXTRA column, that's <i>often</i> bad! If "possible_keys" is NULL, that's often bad (small sorts and temporary tables are tolerable though). An "obvious" index may not actually be used due to poor "selectivity". See the <a href="/wiki/MediaWiki_Engineering/Guides/Measure_backend_performance" title="MediaWiki Engineering/Guides/Measure backend performance">Measure backend performance in production</a> guide, and for more details, see <a href="/wiki/File:Why_your_extension_will_not_be_enabled_on_Wikimedia_wikis_in_its_current_state_and_what_you_can_do_about_it.pdf" title="File:Why your extension will not be enabled on Wikimedia wikis in its current state and what you can do about it.pdf">Roan Kattouw's 2010 talk on security, scalability and performance for extension developers</a>, <a href="https://www.mediawiki.org/wiki/Manual:Database_layout/MySQL_Optimization/Tutorial" class="extiw" title="mw:Manual:Database layout/MySQL Optimization/Tutorial">Roan's MySQL optimization tutorial from 2012</a> (<a href="/wiki/File:SQL_indexing_Tutorial.pdf" title="File:SQL indexing Tutorial.pdf">slides</a>), and <a rel="nofollow" class="external text" href="http://tstarling.com/presentations/Tim%20Performance%202013.pdf">Tim Starling's 2013 performance talk</a>. </p><p>Indexing is not a silver bullet; more isn't always better. Once an index gets big enough that it doesn't fit into RAM anymore, it slows down dramatically. Additionally, an index can make reads faster, but writes slower. </p><p>Good example: See <a href="https://gerrit.wikimedia.org/r/plugins/gitiles/mediawiki/core/%2B/refs/heads/master/maintenance/tables-generated.sql" class="extiw" title="gerrit:plugins/gitiles/mediawiki/core/+/refs/heads/master/maintenance/tables-generated.sql">the <code>block</code> and <code>block_target</code></a> (<a href="https://www.mediawiki.org/wiki/Manual:ipblocks_table" class="extiw" title="mw:Manual:ipblocks table">formerly known as</a> <code>ipblocks</code>) and <code>page_props</code> tables. One of them also offers a reverse index, which gives you a cheap alternative to SORT BY. </p><p>Bad example: See <a href="https://gerrit.wikimedia.org/r/c/mediawiki/extensions/Wikibase/%2B/132460/1/repo/includes/store/sql/EntityPerPageTable.php" class="extiw" title="gerrit:c/mediawiki/extensions/Wikibase/+/132460/1/repo/includes/store/sql/EntityPerPageTable.php">this changeset (a fix)</a>. As the note states, "needs to be id/type, not type/id, according to the definition of the relevant index in <code>wikibase.sql</code>: <code>wb_entity_per_page (epp_entity_id, epp_entity_type)</code>". Rather than using the index that was built on the id-and-type combination, the previous code (that this is fixing) attempted to specify an index that was "type-and-id", which did not exist. Thus, MariaDB did not use the index, and thus instead tried to order the table without using the index, which caused the database to try to sort 20 million rows with no index. </p> <div class="mw-heading mw-heading3"><h3 id="Multiple_datacenters" data-mw-thread-id="h-Multiple_datacenters-Persistence_layer"><span data-mw-comment-start="" id="h-Multiple_datacenters-Persistence_layer"></span>Multiple datacenters<span data-mw-comment-end="h-Multiple_datacenters-Persistence_layer"></span></h3></div> <p><a href="/wiki/MediaWiki_at_WMF" title="MediaWiki at WMF">MediaWiki at WMF</a> is actively responding to requests from <a href="/wiki/Performance/Multi-DC_MediaWiki" title="Performance/Multi-DC MediaWiki">multiple data centers</a>. </p><p>MediaWiki requests should only contact other services within the local datacenter, <i>e.g.</i> reading data from local database replicas. </p><p>During HTTP GET/HEAD/OPTIONS requests to MediaWiki, avoid writing to MySQL (aside from DBMainStash), FileRepo/FileBackend (Swift), and LockManager (redis). Exceptional MySQL/Swift writes can be done via the JobQueue. We have configured the JobQueue such that it can independently accept new jobs locally in each data center. Behind the scenes, asynchronous workers will eventually relay and execute the job in the primary DC. That is, the logic of <code>Job::run()</code> executes in the primary DC only where you can safely perform writes to primary database tables. </p><p>Other applications, should be design to handle GET/HEAD/OPTIONS requests in any data center and other HTTP requests (POST/PUT) in at least the primary datacenter (if not any datacenter). Any writes should be synchronously committed within the primary datacenter, with any replication to remote data centers occurring asynchronously. An independent non-MediaWiki API service <i>might</i> be able to run write APIs correctly in multiple data centers at once if it has limited semantics and has no relational integrity dependencies on other source data persistence layers. For example, if the service simply takes end-user input and stores blobs keyed under new UUIDs, with no way for writes to conflict. If updates or deletions are later added as a feature, then Last-Write-Wins might be considered a "correct" approach to handling write conflicts between data centers (<i>e.g.</i> if only one user has permission to change any given blob then all conflicts are self-inflicted). If write conflicts are not manageable, then your API requests need to be routed by our CDN to the <i>primary</i> datacenter only. </p> <div class="mw-heading mw-heading2 ext-discussiontools-init-section"><h2 id="Shared_resources" data-mw-thread-id="h-Shared_resources"><span data-mw-comment-start="" id="h-Shared_resources"></span>Shared resources<span data-mw-comment-end="h-Shared_resources"></span></h2><!--__DTELLIPSISBUTTON__{"threadItem":{"headingLevel":2,"name":"h-","type":"heading","level":0,"id":"h-Shared_resources","replies":["h-Transactions-Shared_resources","h-Long-running_queries-Shared_resources","h-Advisory_locks-Shared_resources","h-Rate_limiting-Shared_resources"]}}--></div> <p>Be mindful that your code is running in an environment with finite resources used to service numerous APIs, some of which most handle high traffic with low latency. Each application server and intermediary proxy layer in handling a request has limited CPU, disk space, disk bandwidth, network bandwidth, and worker threads. There is a finite pool of application and proxy servers. The same holds true of persistence layers such as databases, object stores, queues, and cache servers. </p><p>We recommend thinking of locks and semaphores as an intentionally limited resource as well. When code sits idly waiting for a lock or semaphore during a web response, that code still occupies one of the workers on the backend servers, as well a worker thread on each intermediary layer that is waiting to proxy the response back to the browser (<i>e.g.</i> Apache, Varnish, Envoy, <i>etc.</i>). If too many workers are waiting for locks, this can cause overloads or even outages. </p> <div class="mw-heading mw-heading3"><h3 id="Transactions" data-mw-thread-id="h-Transactions-Shared_resources"><span data-mw-comment-start="" id="h-Transactions-Shared_resources"></span>Transactions<span data-mw-comment-end="h-Transactions-Shared_resources"></span></h3></div> <p>Every web request and every database operation, in general, should <a href="https://www.mediawiki.org/wiki/Database_transactions" class="extiw" title="mw:Database transactions">occur within a transaction</a>. MediaWiki automatically creates a transaction for you during web requests when interacting with the main MySQL databases (we use the default "REPEATABLE-READ" isolation level). This offers you a simpler developer experience, where your code is atomic and consistent by default. For example, when you select an ID from one table, you can rely on being able to find the associated row in another table. This does mean MySQL must hold on to any locks until the web request (and thus the transaction) is completed, which makes us more prone to lock contention. </p><p>MySQL uses "record locks" to lock individual rows (or gaps) that prevent other requests from creating new rows that would be similar or conflict with one your database writes. Gap locks can be confusing to understand and hence cause surprising cases of contention. Refer to <a rel="nofollow" class="external text" href="http://brightbox.com/blog/2013/10/31/on-mysql-locks/"><i>On MySQL locks</i></a> (Kumar, 2013) to learn about gap locks. </p><p>Be careful when mixing the operations of a database transaction with significant operations to other services (<i>e.g.</i> those that take over 10ms). Also, think about the order in which rows are locked due to your writes (<i>e.g.</i> UPDATE and INSERT queries). When you update, delete, or insert rows in the database ask yourself: </p> <ul><li>What table index can mysql use to guarantee that your SELECT query is fast?</li> <li>What row(s) and row gaps will this cause mysql to lock?</li> <li>Are there other functions or classes that write to the same table?</li> <li>What else will happen between the write query (and thus the lock) and the end of the web response? (when the transaction is committed).</li></ul> <p>You can choose to instruct MySQL to explicitly lock a row or gap during a SELECT and return the latest committed data (plus any changes pending in the current transaction as always). This can be useful when reading data that will be used to determine the next update queries. This can be done via "SELECT ... FOR UPDATE". Note that this can lock the same way that an UPDATE query with the same WHERE clause would. The <b>use of <code>LOCK IN SHARE MODE</code> is strongly discouraged</b> as it can lead to deadlocks. The use of LOCK TABLES is considered problematic and not supported on our platform. </p><p>Each individual write query should normally take less than 5ms to complete, even on the largest production databases. This may require designing and indexing for that during the planning phase, which DBAs can help you with. </p><p>Each overall transaction (all writes from one web request) should normally stay open less than 250ms in total. This is important to reduce contention and thus how long other requests may be required to wait. Try to move updates made to highly contentious tables to the end of the request (<i>e.g.</i> via DeferredUpdates). For example, an API request that stores an uploaded file in Swift (which may take a while if the file is large) and also updates a row in the database, we recommend performing the file operation first and then the database write. This reduces the time between the start of the database transaction and the end of the request. </p><p>Be mindful of <a href="https://www.mediawiki.org/wiki/Manual:Hooks" class="extiw" title="mw:Manual:Hooks">hooks</a> that often run at various points through the web response, which extensions can use to implement additional logic and that may run extra database queries as part of that hook. To minimise the risk of timeouts, deadlocks, and non-atomic updates, aim for speed and simplicity for any database writes during the <a href="https://www.mediawiki.org/wiki/Database_transactions" class="extiw" title="mw:Database transactions">main transaction round</a>. Updates that take non-trivial time or are complex should use post-send DeferredUpdates or the JobQueue when possible, to better isolate different features from one another. Use purges to invalidate caches rather than in-place cache updates. </p><p><b>Example</b>: Here's a common mistake that can cause inappropriate locking. The <code>user_properties</code> table follows the three-column "Entity-attribute-value" pattern: </p> <ol><li>user ID (entity).</li> <li>user preference name (attribute).</li> <li>user preference value.</li> <li>Primary key: user ID + preference name.</li></ol> <p>When saving a change, it may be tempting to delete all existing rows for the user ID, and insert everything you know about the new state. But, this would cause database contention. What MediaWiki does instead is to only make changes to the table by the unique primary key for each row. First, select existing rows, then, we use "upsert" for the rows that have changed (which adds missing rows and replaces existing ones), and lastly "delete" the rows that are no longer needed. This is an important example where more code and more queries produces a significantly faster and more efficient outcome at scale, despite perhaps feeling slower when measured in isolation as a single action in an empty world. </p><p><b>Transaction guidelines</b>: </p> <ul><li>Wrap write operations that are meant to be atomic in <code>IDatabase::startAtomic()</code> and <code>IDatabase::endAtomic()</code> for each affected database. These methods can nest, so atomic method A can invoke atomic methods B, C, and D. Declaring an atomic section is most needed is most needed when the code runs in a CLI mode (automatic transactions are disabled). Only use <code>IDatabase::ATOMIC_CANCELABLE</code> if you need to call <code>IDatabase::cancelAtomic()</code> in some cases as it incurs extra round trips.</li> <li>When reading data via SELECT to determine what write query to issue, you should normally use "SELECT ... FOR UPDATE". Avoid using "LOCK IN SHARE MODE".</li> <li>APIs for making multiple changes at once should <b>take stable identifiers as input</b> whenever possible. IDs are preferred over names, because operating on multiple (mutable) names is prone to race conditions and deadlocks. If deadlocks or lock wait timeouts become a problem, consider using one of the following strategies (in order of appearance). <ol><li>Consider moving high-contention row writes to the end of the transaction via <code>IDatabase::onTransactionPrecommitOrIdle()</code>.</li> <li>Consider moving slow non-database operations to before high-contention database queries. If it is OK for the database operation to sometimes succeed while the non-database operation failed, consider using a DeferredUpdate.</li> <li>Consider moving slow database operations to a DeferredUpdate if they do not need to be highly atomic with respect to the main changes.</li> <li>If the records must be implicitly specified by a hierarchical relationship (<i>e.g.</i> "all comment rows for this thread row"), consider first locking the parent record via "SELECT ... FOR UPDATE" at the start of the operation. This can reduce deadlocks caused by concurrent operations to similar parts of the underlying B*-Trees in MySQL.</li> <li>If the records must be implicitly specified based on a broader conditional (<i>e.g.</i> inequalities, JOINs, sub-queries), consider selecting the ID of the matching rows first, without "FOR UPDATE" and then changing the rows with those IDs that <i>still</i> match the condition (<i>e.g.</i> restate any WHERE clause). This can reduce contention and deadlocks caused by MySQL "gap locking". Note other concurrent transactions can insert rows that would have matched the condition and commit before the transaction ends. Any associated risk to the API semantics should be considered. Sometimes, leaving out the restated WHERE in an UPDATE/DELETE query can reduce contention (<i>e.g.</i> if query planner index use is poor), but this increases the risk of anomalies (<i>e.g.</i> changing rows that no longer matched due to concurrent transaction writes).</li> <li>For tables with a parent/child relationship (<i>e.g.</i> page/revision or thread/comment), where the parent table has a "touched"/"CAS" column, a hybrid of the two above approaches can be used. If all operations to a child table row first do a "SELECT ... FOR UPDATE" of the corresponding parent table row, then check that the SELECT without "FOR UPDATE" yields the same values for "touched"/"CAS" column, and update that column by the end of the operation, then the child table writes could first SELECT the IDs of the matching rows, without FOR UPDATE, and then change them in second query.</li> <li>A variation of the above pattern uses <code>ILoadBalancer::CONN_TRX_AUTOCOMMIT</code> database handles and replaces FOR UPDATE with <code>IDatabase::getScopedLock()</code> calls on keys named after each specified parent row ID (<i>e.g.</i> "page-&lt;ID>").</li></ol></li></ul> <div class="mw-heading mw-heading3"><h3 id="Long-running_queries" data-mw-thread-id="h-Long-running_queries-Shared_resources"><span data-mw-comment-start="" id="h-Long-running_queries-Shared_resources"></span>Long-running queries<span data-mw-comment-end="h-Long-running_queries-Shared_resources"></span></h3></div> <p>We generally develop service classes and database queries with the expectation that they will run in response to a web request. Other contexts where your code may get called are <a href="https://www.mediawiki.org/wiki/Manual:Job_queue/For_developers" class="extiw" title="mw:Manual:Job queue/For developers">JobQueue jobs</a> and command-line <a href="https://www.mediawiki.org/wiki/Manual:Maintenance_scripts" class="extiw" title="mw:Manual:Maintenance scripts">Maintenance scripts</a>. </p><p>MySQL uses snapshots for SELECT queries, and the snapshotting persists until the end of the database connection or transaction. Snapshots implement "REPEATABLE-READ" semantics which ensures that within your query session, you see the database as it existed at a single point in time (the time of the first SELECT). Keeping one connection open for more than a few seconds is generally problematic on regular database replicas. Long connections cause MySQL to create a temporary copy of all rows to remember during your connection, because you might query them later. </p><p>Queries that select or read data (5 seconds or more) must be run offline and via database hosts dedicated for that purpose. </p><p>Note that when assessing whether your queries will take "a long time" or cause contention, <a href="/wiki/MediaWiki_Engineering/Guides/Measure_backend_performance" title="MediaWiki Engineering/Guides/Measure backend performance">measure them</a>. These numbers will always be relative to the performance of the server in general, and to how often it will be run. You can also search the DBPerformance channel on logstash for the URLs or table names handled by your code. </p><p>Good example: </p> <ul><li>Special pages that display data based on complex queries are generated periodically by a maintenance script, run via a cron job. The queries in question use the <a class="external text" href="https://doc.wikimedia.org/mediawiki-core/master/php/md_docs_database.html#autotoc_md40">"vslow" query group</a>, which directs the connection to a live database replica in production set aside for slow queries. See also: <code>updateSpecialPages.php</code>, <a class="external text" href="https://doc.wikimedia.org/mediawiki-core/master/php/classQueryPage.html#a04901141ffaec6344a2402472e3b0d5e">QueryPage::isExpensive</a>, and <code>$wgMiserMode</code>.</li> <li>Analytics reports are standalone and separate from any deployed MediaWiki core. These are generated via periodic cron jobs on <a href="/wiki/Analytics/Systems/Clients" class="mw-redirect" title="Analytics/Systems/Clients">analytics clients</a> (aka "stat" machine) that query the <a href="/wiki/Analytics/Systems/MariaDB" class="mw-redirect" title="Analytics/Systems/MariaDB">Analytics DB Replicas</a>, which contain a full un-redacted near-realtime mirror of the production MediaWiki databases.</li></ul> <div class="mw-heading mw-heading3"><h3 id="Advisory_locks" data-mw-thread-id="h-Advisory_locks-Shared_resources"><span data-mw-comment-start="" id="h-Advisory_locks-Shared_resources"></span>Advisory locks<span data-mw-comment-end="h-Advisory_locks-Shared_resources"></span></h3></div> <p>Beware of common pitfalls around simple advisory locks (<i>e.g.</i> form LockManager, PoolCounter, BagOStuff::getScopedLock, or Database::getScopedLock). </p><p>Make your lock key as granular and narrow as possible to allow high concurrency of other similar operations that don't conflict. </p><p>Likewise, aim to hold the lock for a short duration by releasing it as soon as the critical operation is done. This allows high throughput of operations that modify the same data. When a burst of requests all need to modify the same data (<i>e.g.</i> traffic spike about the same subject, or from the same bot account), each of those will be waiting in their respective "lock" function call for the previous one. The sooner you can release a lock the better! </p><p>Blocking locks are prone to deadlocks, which is a race condition where two requests are both waiting and can't continue (<i>e.g.</i> request 1 waits for lock B held by request 2, and request 2 waits for lock A held by request 1). This is more likely to happen if you have multiple different locks throughout your code execution. If possible, organise the code such that its locks can be acquired up front and then later released all at once. This way, once you have the locks, your code can't get stuck half-way. </p><p>Blocking locks tend to be used when code is written such that each conflicting operation is performed one after the other (additive/incremental). </p><p>Whenever possible, prefer non-blocking locks. This is possible when the code is designed such that operations can be performed by a single server at any given time, and servers fallback to doing nothing or showing an error message. For example, can the code work such that multiple refreshes of the same data are redundant? Or changes that overwrite data would have one party lose either way. </p> <div class="mw-heading mw-heading3"><h3 id="Rate_limiting" data-mw-thread-id="h-Rate_limiting-Shared_resources"><span data-mw-comment-start="" id="h-Rate_limiting-Shared_resources"></span>Rate limiting<span data-mw-comment-end="h-Rate_limiting-Shared_resources"></span></h3></div> <p>If your product exposes new user actions that make database modifications beyond the standard page creation / page editing mechanism, then firstly consider whether this is appropriate and scalable. You're going to have a lot less maintenance overhead and operational risk if you adopt <a href="https://www.mediawiki.org/wiki/Everything_is_a_wiki_page" class="extiw" title="mw:Everything is a wiki page">"Everything is a wiki page"</a>. See also <a rel="nofollow" class="external text" href="https://mcfunley.com/choose-boring-technology">Choose boring technology</a> by Dan McKinley. </p><p>If you do have to expose new "write" actions, make sure a rate limit is applied. </p><p>Example: </p> <ul><li>UrlShortener exposes API to create new short URLs, which needs a rate limit. Typically powered by <code>User::pingLimiter</code>. <a href="https://phabricator.wikimedia.org/T133109" class="extiw" title="phab:T133109">T133109</a></li></ul> <p>For slow or expensive computations that involve no database writes, consider implementing a throttle based on <a href="/wiki/PoolCounter" title="PoolCounter">PoolCounter</a> to limit overall server load. </p><p>Example: </p> <ul><li>Special:Contributions exposes a database read query that can be slow. This is rate limited by PoolCounter. See also <a href="https://phabricator.wikimedia.org/T234450" class="extiw" title="phab:T234450">T234450</a> and <a href="https://phabricator.wikimedia.org/T160985" class="extiw" title="phab:T160985">T160985</a>.</li></ul> <div class="mw-heading mw-heading2 ext-discussiontools-init-section"><h2 id="Further_reading" data-mw-thread-id="h-Further_reading"><span data-mw-comment-start="" id="h-Further_reading"></span>Further reading<span data-mw-comment-end="h-Further_reading"></span></h2><!--__DTELLIPSISBUTTON__{"threadItem":{"headingLevel":2,"name":"h-","type":"heading","level":0,"id":"h-Further_reading","replies":["h-Articles-Further_reading","h-Talks-Further_reading","h-ResourceLoader-Further_reading","h-Meta-Further_reading"]}}--></div> <ul><li><b><a href="/wiki/MediaWiki_Engineering/Guides/Measure_backend_performance" title="MediaWiki Engineering/Guides/Measure backend performance">How to measure backend performance</a></b>.</li> <li><a href="/wiki/Performance/Guides/PHP_optimisation_tips" class="mw-redirect" title="Performance/Guides/PHP optimisation tips"><b>PHP optimisation tips</b></a></li> <li><a href="/wiki/Memcached_for_MediaWiki" title="Memcached for MediaWiki">Memcached at WMF</a>, for general principles and advice on how we expect MediaWiki developers to use Memcached.</li> <li><a href="https://www.mediawiki.org/wiki/Manual:Job_queue/For_developers" class="extiw" title="mw:Manual:Job queue/For developers">Manual:Job queue/For developers</a> (<a class="external text" href="https://doc.wikimedia.org/mediawiki-core/master/php/jobqueuearch.html">JobQueue architecture reference</a>)</li> <li><a href="https://www.mediawiki.org/wiki/API:Etiquette" class="extiw" title="mw:API:Etiquette">API:Etiquette</a></li> <li><a href="https://www.mediawiki.org/wiki/Architecture_guidelines" class="extiw" title="mw:Architecture guidelines">Architecture guidelines</a></li> <li><a href="https://www.mediawiki.org/wiki/Security_for_developers/Architecture" class="extiw" title="mw:Security for developers/Architecture">Security for developers/Architecture</a></li></ul> <div class="mw-heading mw-heading3"><h3 id="Articles" data-mw-thread-id="h-Articles-Further_reading"><span data-mw-comment-start="" id="h-Articles-Further_reading"></span>Articles<span data-mw-comment-end="h-Articles-Further_reading"></span></h3></div> <ul><li><a class="external text" href="https://blog.wikimedia.org/2012/03/15/measuring-site-performance-at-the-wikimedia-foundation/">"Measuring Site Performance at the Wikimedia Foundation"</a>, Asher Feldman, March 2012</li> <li><a class="external text" href="https://blog.wikimedia.org/2013/02/05/how-the-technical-operations-team-stops-problems-in-their-tracks/">"How the Technical Operations team stops problems in their tracks"</a>, Sumana Harihareswara, February 2013</li></ul> <ul><li><a rel="nofollow" class="external text" href="http://aosabook.org/en/distsys.html">"Scalable Web Architecture and Distributed Systems"</a> (book chapter), Kate Matsudaira, May 2012</li> <li><a rel="nofollow" class="external text" href="http://ljungblad.nu/post/83400324746/80-of-end-user-response-time-is-spent-on-the-frontend">"80% of end-user response time is spent on the frontend"</a>, Marcus Ljungblad, April 2014</li></ul> <div class="mw-heading mw-heading3"><h3 id="Talks" data-mw-thread-id="h-Talks-Further_reading"><span data-mw-comment-start="" id="h-Talks-Further_reading"></span>Talks<span data-mw-comment-end="h-Talks-Further_reading"></span></h3></div> <ul><li><a href="/wiki/File:Why_your_extension_will_not_be_enabled_on_Wikimedia_wikis_in_its_current_state_and_what_you_can_do_about_it.pdf" title="File:Why your extension will not be enabled on Wikimedia wikis in its current state and what you can do about it.pdf">"Why your extension will not be enabled on Wikimedia wikis in its current state and what you can do about it"</a>, Roan Kattouw, Wikimania, July 2010</li> <li><a href="https://www.mediawiki.org/wiki/Code_review_management/July_2011_training#Tim.27s_security_and_performance_talk" class="extiw" title="mw:Code review management/July 2011 training">Notes from Tim Starling's security and performance talk</a>, WMF training session, July 2011</li> <li><a href="https://www.mediawiki.org/wiki/Manual:Database_layout/MySQL_Optimization/Tutorial" class="extiw" title="mw:Manual:Database layout/MySQL Optimization/Tutorial">MediaWiki MySQL optimization tutorial</a> (<a href="/wiki/File:SQL_indexing_Tutorial.pdf" title="File:SQL indexing Tutorial.pdf">slides</a>), Roan Kattouw, Berlin Hackathon, June 2012</li> <li><a href="/wiki/File:MediaWiki_Performance_Profiling.ogv" title="File:MediaWiki Performance Profiling.ogv">"MediaWiki Performance Profiling"</a> (video) (<a href="/wiki/File:MediaWikiPerformanceProfiling.pdf" title="File:MediaWikiPerformanceProfiling.pdf">slides</a>), Asher Feldman, WMF Tech Days, September 2012</li> <li><a rel="nofollow" class="external text" href="http://tstarling.com/presentations/Tim%20Performance%202013.pdf">"MediaWiki Performance Techniques"</a>, Tim Starling, Amsterdam Hackathon, May 2013</li> <li><a rel="nofollow" class="external text" href="https://www.youtube.com/watch?v=acZ3SwbhsaM">"Let's talk about web performance"</a> (video), Peter Hedenskog, WMF tech talk, August 2015</li></ul> <div class="mw-heading mw-heading3"><h3 id="ResourceLoader" data-mw-thread-id="h-ResourceLoader-Further_reading"><span data-mw-comment-start="" id="h-ResourceLoader-Further_reading"></span>ResourceLoader<span data-mw-comment-end="h-ResourceLoader-Further_reading"></span></h3></div> <p>For frontend performance guidelines, see <a href="/wiki/MediaWiki_Engineering/Guides/Frontend_performance_practices" title="MediaWiki Engineering/Guides/Frontend performance practices">MediaWiki Engineering/Guides/Frontend performance practices</a>. </p> <div class="mw-heading mw-heading3"><h3 id="Meta" data-mw-thread-id="h-Meta-Further_reading"><span data-mw-comment-start="" id="h-Meta-Further_reading"></span>Meta<span data-mw-comment-end="h-Meta-Further_reading"></span></h3></div> <p>Sources that helped influence these guidelines, and future drafts and ideas: </p> <ul><li><a href="https://www.mediawiki.org/wiki/Requests_for_comment/Performance_standards_for_new_features" class="extiw" title="mw:Requests for comment/Performance standards for new features">RRC: Performance standards for new features</a>, Ori Livneh, December 2013.</li> <li><a href="https://www.mediawiki.org/wiki/Architecture_Summit_2014/Performance" class="extiw" title="mw:Architecture Summit 2014/Performance">Notes from performance discussion</a>, Wikimedia Architecture Summit 2014, January 2014.</li></ul> <div class="mw-heading mw-heading2 ext-discussiontools-init-section"><h2 id="References" data-mw-thread-id="h-References"><span data-mw-comment-start="" id="h-References"></span>References<span data-mw-comment-end="h-References"></span></h2><!--__DTELLIPSISBUTTON__{"threadItem":{"headingLevel":2,"name":"h-","type":"heading","level":0,"id":"h-References","replies":[]}}--></div> <ol class="references"> <li id="cite_note-1"><span class="mw-cite-backlink"><a href="#cite_ref-1">↑</a></span> <span class="reference-text"><a rel="nofollow" class="external text" href="https://timotijhof.net/posts/2018/measuring-wikipedia-page-load-times/">Measuring Wikipedia page load times</a> (2018), Timo Tijhof.</span> </li> <li id="cite_note-2"><span class="mw-cite-backlink"><a href="#cite_ref-2">↑</a></span> <span class="reference-text"><a rel="nofollow" class="external text" href="https://hpbn.co/primer-on-web-performance/#analyzing-real-user-measurement-data">HPBN Book, Chapter: Primer on Web Performance</a> (2013), Ilya Grigorik.</span> </li> <li id="cite_note-3"><span class="mw-cite-backlink"><a href="#cite_ref-3">↑</a></span> <span class="reference-text"><a rel="nofollow" class="external text" href="https://www.infoq.com/presentations/latency-response-time/">“How Not To Measure Latency”</a> (2016), a tech talk by Gil Tene.</span> </li> <li id="cite_note-4"><span class="mw-cite-backlink"><a href="#cite_ref-4">↑</a></span> <span class="reference-text">For more information about the sad stable of media storage, refer to task <a href="https://phabricator.wikimedia.org/T28741" class="extiw" title="phab:T28741">T28741</a> and the various parent tasks and "Mentions" tasks detailing its problems.</span> </li> </ol> <!-- NewPP limit report Parsed by mw‐api‐int.codfw.main‐849f99967d‐f8f5b Cached time: 20241122190518 Cache expiry: 2592000 Reduced expiry: false Complications: [show‐toc] DiscussionTools time usage: 0.028 seconds CPU time usage: 0.123 seconds Real time usage: 0.139 seconds Preprocessor visited node count: 624/1000000 Post‐expand include size: 9353/2097152 bytes Template argument size: 2666/2097152 bytes Highest expansion depth: 8/100 Expensive parser function count: 0/500 Unstrip recursion depth: 0/20 Unstrip post‐expand size: 4091/5000000 bytes --> <!-- Transclusion expansion time report (%,ms,calls,template) 100.00% 34.366 1 -total 76.84% 26.407 1 Template:Navigation_Wikimedia_infrastructure 40.96% 14.076 1 Template:Navigation_sidebar 7.06% 2.425 1 Template:TOC 4.60% 1.582 1 Template:Main --> <!-- Saved in parser cache with key labswiki:pcache:idhash:451802-0!canonical and timestamp 20241122190518 and revision id 2233113. Rendering was triggered because: api-parse --> </div><!--esi <esi:include src="/esitest-fa8a495983347898/content" /> --><noscript><img src="https://login.wikimedia.org/wiki/Special:CentralAutoLogin/start?type=1x1" alt="" width="1" height="1" style="border: none; position: absolute;"></noscript> <div class="printfooter" data-nosnippet="">Retrieved from "<a dir="ltr" href="https://wikitech.wikimedia.org/w/index.php?title=MediaWiki_Engineering/Guides/Backend_performance_practices&amp;oldid=2233113">https://wikitech.wikimedia.org/w/index.php?title=MediaWiki_Engineering/Guides/Backend_performance_practices&amp;oldid=2233113</a>"</div></div> <div id="catlinks" class="catlinks" data-mw="interface"><div id="mw-normal-catlinks" class="mw-normal-catlinks"><a href="/wiki/Special:Categories" title="Special:Categories">Category</a>: <ul><li><a href="/wiki/Category:MediaWiki_Engineering" title="Category:MediaWiki Engineering">MediaWiki Engineering</a></li></ul></div></div> </div> </main> </div> <div class="mw-footer-container"> <footer id="footer" class="mw-footer" > <ul id="footer-info"> <li id="footer-info-lastmod"> This page was last edited on 8 October 2024, at 08:38.</li> <li id="footer-info-copyright">Text is available under the <a rel="nofollow" class="external text" href="https://creativecommons.org/licenses/by-sa/4.0/deed.en">Creative Commons Attribution-ShareAlike License</a>; additional terms may apply. See <a class="external text" href="https://foundation.wikimedia.org/wiki/Special:MyLanguage/Policy:Terms_of_Use">Terms of Use</a> for details.</li> </ul> <ul id="footer-places"> <li id="footer-places-privacy"><a href="https://foundation.wikimedia.org/wiki/Special:MyLanguage/Policy:Privacy_policy">Privacy policy</a></li> <li id="footer-places-about"><a href="/wiki/Main_Page">About Wikitech</a></li> <li id="footer-places-disclaimers"><a href="https://foundation.wikimedia.org/wiki/General_disclaimer">Disclaimers</a></li> <li id="footer-places-wm-codeofconduct"><a href="https://www.mediawiki.org/wiki/Special:MyLanguage/Code_of_Conduct">Code of Conduct</a></li> <li id="footer-places-developers"><a href="https://developer.wikimedia.org">Developers</a></li> <li id="footer-places-statslink"><a href="https://stats.wikimedia.org/#/wikitech.wikimedia.org">Statistics</a></li> <li id="footer-places-cookiestatement"><a href="https://foundation.wikimedia.org/wiki/Special:MyLanguage/Policy:Cookie_statement">Cookie statement</a></li> <li id="footer-places-mobileview"><a href="//wikitech.wikimedia.org/w/index.php?title=MediaWiki_Engineering/Guides/Backend_performance_practices&amp;mobileaction=toggle_view_mobile" class="noprint stopMobileRedirectToggle">Mobile view</a></li> </ul> <ul id="footer-icons" class="noprint"> <li id="footer-copyrightico"><a href="https://wikimediafoundation.org/" class="cdx-button cdx-button--fake-button cdx-button--size-large cdx-button--fake-button--enabled"><img src="/static/images/footer/wikimedia-button.svg" width="84" height="29" alt="Wikimedia Foundation" loading="lazy"></a></li> <li id="footer-poweredbyico"><a href="https://www.mediawiki.org/" class="cdx-button cdx-button--fake-button cdx-button--size-large cdx-button--fake-button--enabled"><img src="/w/resources/assets/poweredby_mediawiki.svg" alt="Powered by MediaWiki" width="88" height="31" loading="lazy"></a></li> </ul> </footer> </div> </div> </div> <div class="vector-settings" id="p-dock-bottom"> <ul></ul> </div><script>(RLQ=window.RLQ||[]).push(function(){mw.config.set({"wgHostname":"mw-web.codfw.main-f69cdc8f6-zpn8c","wgBackendResponseTime":99,"wgDiscussionToolsPageThreads":[{"headingLevel":2,"name":"h-","type":"heading","level":0,"id":"h-Getting_started","replies":[{"headingLevel":3,"name":"h-","type":"heading","level":0,"id":"h-General_principles-Getting_started","replies":[]},{"headingLevel":3,"name":"h-","type":"heading","level":0,"id":"h-Ballpark_numbers-Getting_started","replies":[]},{"headingLevel":3,"name":"h-","type":"heading","level":0,"id":"h-Percentiles-Getting_started","replies":[]},{"headingLevel":3,"name":"h-","type":"heading","level":0,"id":"h-Latency-Getting_started","replies":[]}]},{"headingLevel":2,"name":"h-","type":"heading","level":0,"id":"h-How_often_will_my_code_run?","replies":[{"headingLevel":3,"name":"h-","type":"heading","level":0,"id":"h-Caching_layers-How_often_will_my_code_run?","replies":[]},{"headingLevel":3,"name":"h-","type":"heading","level":0,"id":"h-Cookies-How_often_will_my_code_run?","replies":[]},{"headingLevel":3,"name":"h-","type":"heading","level":0,"id":"h-Design_for_cache_miss-How_often_will_my_code_run?","replies":[]},{"headingLevel":3,"name":"h-","type":"heading","level":0,"id":"h-Practical_examples_about_caching_layers-How_often_will_my_code_run?","replies":[]}]},{"headingLevel":2,"name":"h-","type":"heading","level":0,"id":"h-Leverage_the_platform!","replies":[]},{"headingLevel":2,"name":"h-","type":"heading","level":0,"id":"h-You_are_not_alone","replies":[]},{"headingLevel":2,"name":"h-","type":"heading","level":0,"id":"h-Persistence_layer","replies":[{"headingLevel":3,"name":"h-","type":"heading","level":0,"id":"h-Schema_and_API_design-Persistence_layer","replies":[]},{"headingLevel":3,"name":"h-","type":"heading","level":0,"id":"h-Practical_examples_of_persistence-Persistence_layer","replies":[{"headingLevel":4,"name":"h-","type":"heading","level":0,"id":"h-Example:_Permanent_names-Practical_examples_of_persistence","replies":[]},{"headingLevel":4,"name":"h-","type":"heading","level":0,"id":"h-Example:_Large_object_size-Practical_examples_of_persistence","replies":[]},{"headingLevel":4,"name":"h-","type":"heading","level":0,"id":"h-Example:_Job_queue_use_cases-Practical_examples_of_persistence","replies":[]},{"headingLevel":4,"name":"h-","type":"heading","level":0,"id":"h-Example:_Send_a_notification-Practical_examples_of_persistence","replies":[]},{"headingLevel":4,"name":"h-","type":"heading","level":0,"id":"h-Example:_Count_active_users-Practical_examples_of_persistence","replies":[]}]},{"headingLevel":3,"name":"h-","type":"heading","level":0,"id":"h-Indexing-Persistence_layer","replies":[]},{"headingLevel":3,"name":"h-","type":"heading","level":0,"id":"h-Multiple_datacenters-Persistence_layer","replies":[]}]},{"headingLevel":2,"name":"h-","type":"heading","level":0,"id":"h-Shared_resources","replies":[{"headingLevel":3,"name":"h-","type":"heading","level":0,"id":"h-Transactions-Shared_resources","replies":[]},{"headingLevel":3,"name":"h-","type":"heading","level":0,"id":"h-Long-running_queries-Shared_resources","replies":[]},{"headingLevel":3,"name":"h-","type":"heading","level":0,"id":"h-Advisory_locks-Shared_resources","replies":[]},{"headingLevel":3,"name":"h-","type":"heading","level":0,"id":"h-Rate_limiting-Shared_resources","replies":[]}]},{"headingLevel":2,"name":"h-","type":"heading","level":0,"id":"h-Further_reading","replies":[{"headingLevel":3,"name":"h-","type":"heading","level":0,"id":"h-Articles-Further_reading","replies":[]},{"headingLevel":3,"name":"h-","type":"heading","level":0,"id":"h-Talks-Further_reading","replies":[]},{"headingLevel":3,"name":"h-","type":"heading","level":0,"id":"h-ResourceLoader-Further_reading","replies":[]},{"headingLevel":3,"name":"h-","type":"heading","level":0,"id":"h-Meta-Further_reading","replies":[]}]},{"headingLevel":2,"name":"h-","type":"heading","level":0,"id":"h-References","replies":[]}],"wgPageParseReport":{"discussiontools":{"limitreport-timeusage":"0.028"},"limitreport":{"cputime":"0.123","walltime":"0.139","ppvisitednodes":{"value":624,"limit":1000000},"postexpandincludesize":{"value":9353,"limit":2097152},"templateargumentsize":{"value":2666,"limit":2097152},"expansiondepth":{"value":8,"limit":100},"expensivefunctioncount":{"value":0,"limit":500},"unstrip-depth":{"value":0,"limit":20},"unstrip-size":{"value":4091,"limit":5000000},"timingprofile":["100.00% 34.366 1 -total"," 76.84% 26.407 1 Template:Navigation_Wikimedia_infrastructure"," 40.96% 14.076 1 Template:Navigation_sidebar"," 7.06% 2.425 1 Template:TOC"," 4.60% 1.582 1 Template:Main"]},"cachereport":{"origin":"mw-api-int.codfw.main-849f99967d-f8f5b","timestamp":"20241122190518","ttl":2592000,"transientcontent":false}}});});</script> </body> </html>

Pages: 1 2 3 4 5 6 7 8 9 10