Skip to main content

From Malloc to Match (Part 1): The Evolution of PHP Through the Lens of a Systems Programmer

Photo of Jeremy Andrews
Jeremy Andrews - Founding Partner/CEO
October 28, 2026
Jeremy Andrews, Tag1's Founding Partner and CEO, looks at PHP's early design through a former kernel developer's eyes, from hash tables for nearly everything to Drupal hooks built from strings. He shows what those trades cost Drupal 6 and 7, including hook lookups that took around 10% of page execution time, and how caching kept busy sites running.

This is Part 1 of a two-part series about the trades PHP made early on and what they have meant for Drupal performance. Part 2 covers the Java-fication of Drupal, the engine-level work that saved PHP from itself, and how Drupal came full circle, with its biggest performance gains in a decade.

I met Rasmus Lerdorf, the creator of PHP, in 2007 at the OSCMS Summit in Sunnyvale, held on the Yahoo campus. I was in town to co-present at a LAMP performance seminar held the day after the summit, and I almost didn't make it into the summit itself, as it was apparently filled to capacity and someone more well-known had to vouch for me at the door. This led to me sitting directly across from Rasmus at lunch without even knowing who he was, and we had a long conversation about PHP and its origins. His talk at the summit was called "Performance and Security,"[1] and it walked through profiling PHP with callgrind and Xdebug before moving on to cross-site scripting (he found a hole in pretty much every CMS in the room). What stuck with me was when he profiled Drupal and, as I remember it, showed that more than half of each request was spent bootstrapping, simply loading Drupal into memory before it could even start querying the database or rendering the page. That was pretty shocking to me.

Rasmus Lerdorf, the creator of PHP, wearing a black PHP T-shirt at the 2007 OSCMS Summit.

Rasmus Lerdorf at the OSCMS Summit, Sunnyvale, 2007
Photo: Dries Buytaert, CC BY 4.0

I've been working with Drupal since early 2002, a few months before the 4.0.0 release, and Drupal is what introduced me to PHP. Before that I was a kernel developer, writing packet filters, kernel modules, and network stacks in C at a firewall company. I've told that origin story elsewhere; the short version is that I had a website that was crashing under its own traffic, and one of my readers, Dries Buytaert, reached out to tell me about this CMS he'd built. I switched to Drupal and, one thing leading to another, I found myself worrying less and less about null pointers.

A kernel developer is one kind of systems programmer, someone who writes the software that other software runs on: operating systems, device drivers, databases, and language runtimes like PHP's own engine. Most of the job is thinking about what the computer is physically doing, like when memory gets allocated and freed, how data is laid out, what the CPU actually executes, and where the time goes underneath the code you wrote. That way of thinking carries over to Drupal, because PHP is itself a C program, and plenty of Drupal slowdowns only make sense one layer down, in PHP or below it. Back in 2009, for example, we wrote about running strace (a tool that lists every system call, or request to the operating system, that a program makes) on drupal.org's web servers, where PHP was spending nearly half of its system call time checking file paths on network storage, and raising one PHP setting, the size of the realpath cache (a record of file paths already looked up), made most of that go away. Being able to look at that layer is one part of how we find and fix performance problems at Tag1.

Drupal turned 25 this January,[2] and PHP turned 30 last year (Rasmus first posted "Personal Home Page Tools" to a Usenet group on June 8, 1995).[3] I've also been writing a lot of Rust lately, which has a way of making you rethink every assumption about what a programming language should and shouldn't do for you. That said, I'm not going to focus on Rust in this blog, and I'm also not going to attack PHP. Instead, this is a reflection on how a language that started as a thin wrapper around C's standard library evolved into something that powers most of the web[4] (giving up nearly everything a systems programmer cares about along the way, which is a big part of what made Drupal possible), and what that evolution looked like from the perspective of someone who still thinks in pointers and structs. It's also a performance story, because those same trades still show up as Drupal performance problems, and working on those problems is how I've spent much of the last twenty years.

At DrupalCon Rotterdam this September, Dries named Drupal's reputation gap as the project's number one challenge: too many people are judging the Drupal of ten years ago, and his answer was to talk louder about what is working.[5] This post spends most of its time on the Drupal of fifteen and twenty years ago. The costs I describe were real, and some can still be found in a profiler, but they're also the costs Drupal and PHP have spent those years paying down, which is most of what Part 2 is about.

(If you're a decision maker and not a C programmer, some of this applies to you directly. If you're still running Drupal 7, you're running the architecture this post describes, and it reached end of life on January 5, 2025; if you're on a PHP release older than 8.2 you're no longer getting security fixes, and 8.2 itself stops getting them at the end of this year.[6])

The C Programmer's Wrapper

For a C programmer entering web development in the late 1990s, PHP was immediately familiar in a way that was both comforting and unsettling. Functions like strlen(), strpos(), and fopen() were direct mappings to their C counterparts. The language felt like someone had taken libc, wrapped it in a parser that could be embedded in HTML, and called it a day. In fact, this is essentially how Rasmus explained it to me over lunch.

PHP 3, released in June 1998 after two developers in Tel Aviv rewrote the parser, was the first version that felt like an actual language rather than a templating tool.[7] Before that, PHP/FI was essentially a C program that parsed HTML and executed embedded tags. PHP 3 also got a new name: "PHP" had started out as "Personal Home Page," and PHP 3 made it the recursive "PHP: Hypertext Preprocessor," a name that turned out to be a lot more future-proof than the original. PHP 3 still executed code more or less as it parsed it, with no separate compile step and no intermediate representation. For someone used to the compile-link-run cycle of C, this parse-and-run approach was painfully slow. But it offered something C never could: you saved the file, refreshed the browser, and saw the result. That feedback loop changed everything about how fast you could build for the web.

For years I was embarrassed to admit I wrote PHP code. "I'm really a kernel developer," I'd tell people, "I swear." But the truth was that I loved how things just worked, without memory leaks, dangling pointers, or three-hour debugging sessions because I forgot to free a buffer.

PHP also introduced one of the worst security decisions in the history of web development: register_globals. When enabled (and it was enabled by default), PHP automatically created global variables from HTTP request parameters. A form field named admin became a global $admin variable. A query string parameter could overwrite a variable you thought you controlled. This was C's lack of memory safety translated into web security terms. In C, a buffer overflow lets an attacker write arbitrary data to memory. In PHP with register_globals, an attacker could inject arbitrary variables into your application's scope simply by manipulating the URL. It took until PHP 4.2 (April 2002) to change the default to off, and until PHP 5.4 (March 2012) to remove the feature entirely. Fourteen years later, the C underneath showed through directly: PHP2Shell, disclosed this month, is a set of memory-safety bugs in PHP's own engine, present in every release since that same PHP 5.4, that let an unauthenticated request take over the server (what PHP site owners should do today).[8]

At the turn of the century, though, seeing the C underneath was mostly a good thing. The two developers behind PHP 3 had rewritten the core again, and that rewrite, the Zend Engine, shipped as PHP 4 in May 2000.[7] Code was now read once, compiled to an internal format (opcodes), and then executed, and the memory model was formalized. Variables were stored in a C struct called a zval (Zend Value) that contained the value, the type, a reference count, and a flag for whether it was a reference. If you understood C structs, you could read the PHP source and see exactly what was happening under the hood, and a lot of what was happening was hash table lookups.

The Hash Table Bargain

The defining architectural decision of PHP, the one that explains both its flexibility and its performance characteristics to this day, is that almost everything is a hash table (a data structure that maps keys to values, like a dictionary where you look up a word to find its definition).

In C, an array is a pointer to a contiguous block of memory. You access an element by computing an offset: base address plus index times element size. It's O(1) and it's fast because the CPU prefetcher knows exactly where the next access will be.

In PHP, an "array" is actually an ordered map, storing the key and the value together and remembering the order they were inserted. Even a simple integer-indexed array went through the hash table machinery in PHP 4 and 5: the integer served as its own hash, but you still found the right bucket, resolved potential collisions (like a dictionary word with multiple definitions that you have to read one at a time to find the right one), and followed a pointer to the value.[9] Symbol tables for variables, function names, class names, and object properties are also all hash tables. In PHP 4, every time a script accessed $x, the engine hashed the string "x" and looked it up. (PHP 5.1 moved that hashing to compile time for local variables, an optimization known as "compiled variables."[10] Functions and classes were still looked up by name in hash tables.)

To a C programmer, this is physically painful. You can feel the overhead. But the utility was undeniable. You could treat an array as a list, a map, a queue, or a stack without writing a single line of memory management code. No malloc, no free, no buffer overflows, and no use-after-free bugs. PHP traded everything a systems programmer values (memory layout, cache locality, deterministic performance) for something C couldn't offer: a world where an amateur could build a working web application without crashing the server.

That trade was worth it in 1998, and by 2007, when I was visiting the Yahoo campus, PHP was the P in LAMP. The overhead never went away entirely, but PHP has spent the years since making that trade a lot cheaper (Part 2 covers how), and the language isn't standing still. PHP 8.5 shipped last November with a pipe operator, and PHP 8.6 is due November 19 with partial function application.[11]

Objects as Values

The other peculiarity for a C programmer in PHP 4 was how objects worked. In C, when you assign a struct pointer to another variable, you're copying the pointer. Both variables point to the same memory. In PHP 4, objects were values. Assigning $obj2 = $obj1 copied the object, meaning modifying $obj2 had no effect on $obj1. To get reference behavior you had to use the explicit reference operator: $obj2 =& $obj1.

This led to codebases littered with ampersands as developers fought against the language's default behavior. Many didn't fully understand what the ampersand was doing; they just knew that without it, modifying an object in one place didn't affect it in another, and with it, things worked the way they expected. So they scattered & through function signatures and assignments like a protective incantation, often in places where it wasn't needed or where it introduced subtle bugs. It was the opposite of what anyone with C experience expected, and it meant unnecessary memory duplication everywhere. PHP 5 would eventually fix this, but for years the community worked around a language that couldn't efficiently share an object between two variables without a special annotation.

Drupal and the Hook System

I was working at the previously mentioned firewall company when Drupal found me in 2002. By November 2005, the firewall company had been acquired, and this being my second corporate merger, I quit. I couldn't suffer through another merger, and so instead started doing freelance Drupal performance consulting. The Drupal I knew during this period was procedural through and through.

Drupal emerged in this era as proof that you could build remarkably complex systems with PHP's messy toolbox. The architecture was purely procedural, built on a system called "hooks."

A hook was string-based dynamic dispatch. If your module wanted to react to a user logging in, you defined a function called mymodule_user(). When Drupal invoked the user hook, it walked the list of enabled modules, glued each module's name onto the hook name, and asked PHP whether a function by that name existed (function_exists(), which is a lookup in the function table, a hash table, naturally).[12] Every match got called in turn. The C equivalent would be something like calling sprintf to build a string, then doing a hash table lookup to find and invoke a function pointer. No vtables, no interfaces, and no compile-time checking of any kind. A typo in your function name, like mymodule_usr() instead of mymodule_user(), meant your code simply never ran. No error or warning, it just didn't exist as far as Drupal was concerned.

This was computationally expensive. The first time a hook ran in a request, Drupal built a string and did a hash lookup for every enabled module; after that it remembered which modules implemented the hook, but it still had to build each function name to call it. And that was just one hook. A single page load invoked a lot of different hooks, and a real site had a lot of modules, so the cost compounded as the site grew, which is why "more modules means a slower site" was common wisdom at the time. When core developers dug into this for Drupal 7 in 2009, they found module_hook() being called about 1,000 times on each page, with it and module_implements() taking up around 10% of page execution time, and noted that adding a few nodes, comments, or modules could increase the number of calls "by a factor of 10 or more."[13] What came out of that work was a persistent cache in Drupal 7 of which modules implement which hooks, and an early version of the patch cut module_hook() calls on an empty front page from 783 to 58, saving about 35 milliseconds.[13] But the hook system was radically accessible. You didn't need to understand class hierarchies or design patterns. You needed to know the naming convention and the available hooks, and you could extend a complex content management system with a few functions in a single file.

It's easy to dismiss something like this from a systems programming perspective, but the Drupal community grew to many thousands of contributors precisely because you could write a useful module after reading one page of documentation.

The Object-Oriented Shift

PHP 5, released in July 2004 with Zend Engine 2.0, fixed the object model.[14] Objects became handles rather than values. Under the hood, an object variable now held an identifier pointing to the actual object data in an object store. In C terms, $obj went from being a struct Object to being something much closer to a struct Object*. Assignment copied the handle, not the data. Both variables pointed to the same object.

This single change aligned PHP with how Java and Python handle objects and enabled a generation of design patterns that depend on shared state. Singletons, registries, dependency injection: none of these work cleanly when object assignment copies data. PHP 5 also introduced visibility modifiers (public, private, protected), which for a C programmer felt like the jump from C to C++. The language was beginning to enforce some discipline.

The 5.x series moved fast from there. PHP 5.1 (November 2005) enabled PDO by default, a unified database access layer that offered one interface in place of the menagerie of database-specific extensions (mysql_, pg_). For a C developer, this was like ODBC: a common interface between your application and the storage engine. It also meant applications could switch databases without rewriting every query function, which Drupal 7 eventually built its entire database layer on. PHP 5.2 (November 2006) added the JSON and filter extensions and proper DateTime objects, less glamorous but the kind of thing every web application ended up needing.

PHP 5.3 in June 2009 was the start of the modern era. Namespaces finally addressed the symbol collision problem that had plagued PHP since the beginning. No more naming functions _field_ui_field_overview_form_validate_add_existing() (a real Drupal 7 function[15]) to avoid conflicts. You could organize code as Vendor\Component\ClassName. The choice of backslash as the namespace separator was contentious and remains one of PHP's more visible oddities, since most languages use dots or double colons. PHP tried double colons first, but foo::bar() could mean either a static method on a class or a function in a namespace, and the engine couldn't reliably tell which, so in October 2008 the backslash won.[16] It's a good example of how PHP's evolution has always been constrained by decisions made when the language was a CGI script generator, since you can't use syntax that conflicts with existing behavior, even when that behavior is a relic.

Closures arrived in the same release, as anonymous functions with explicit variable capture: function() use ($var) { ... }. Internally each closure was an object of the Closure class, and the same release added the __invoke magic method so that any object could be called like a function, which made for a clever design. For a C programmer, closures are essentially function pointers with associated state, similar to what you'd build manually with a struct and a function pointer in C, but without the boilerplate. The explicit use clause is unusual compared to other languages that capture variables automatically, but it makes the data flow visible.

PHP 5.4 (March 2012) added traits for horizontal code reuse and the short array syntax ([] instead of array()). Traits looked a bit like the C preprocessor's #include applied to class methods, a way to share behavior without the constraints of single inheritance and without the complexity of multiple inheritance. PHP 5.5 (June 2013) brought generators and finally, and bundled the OPcache extension, so compiled opcodes could stay in shared memory between requests without installing anything extra. PHP 5.6 (August 2014) rounded out the series with variadic functions, argument unpacking, and constant scalar expressions, bringing the language closer to feature parity with what developers expected from a modern language.

Drupal 6: Procedural as Far as It Would Go

Drupal 6, released in February 2008, deserves attention because it was the version that took the procedural model as far as it could go while still being coherent. The menu system was rewritten from scratch, almost all themeable output moved into separate template files (separating content from presentation, at least in theory), and the new Schema API let modules describe their database tables as arrays instead of shipping hand-written CREATE TABLE statements for MySQL and PostgreSQL.[17]

Drupal already had its own database abstraction by then. Instead of writing raw SQL with string concatenation (which is how SQL injection vulnerabilities are born), developers called db_query() with printf-style placeholders like %d and %s, and Drupal escaped the arguments before building the query. It wasn't true prepared statements, but it made the common case safe by default. Drupal 7 then replaced the whole layer with one built on PDO,[18] and, ironically, the notorious Drupalgeddon vulnerability (SA-CORE-2014-005) was a SQL injection in that new layer itself, where specially crafted array keys let an anonymous user run arbitrary SQL on any Drupal 7 site before 7.32.[19]

Drupal 6 was also the era where the module ecosystem exploded. The combination of hooks for extensibility, a stable API, and the low barrier to entry led to thousands of contributed modules covering everything from e-commerce to media handling. The architectural debt was piling up under the surface, but from a productivity standpoint, Drupal 6 was remarkable. You could build a complex content-driven website faster than with almost any other tool available.

I started Tag1 Consulting in October 2007, the year before Drupal 6 came out. Drupal 6 was the version that made enterprise clients take the platform seriously, so the timing worked out well for us.

When Clarity Digital Group, the owner of Examiner.com, acquired NowPublic, a citizen journalism site built on Drupal that we'd been supporting,[20] we ended up migrating Examiner.com from ColdFusion to Drupal 7, six months before Drupal 7 was even released. Examiner had over 20 million monthly readers and was among the top 100 sites in the United States (the Drupal 7 release announcement put it at 22.4 million monthly unique visitors).[21] Building at that scale on pre-release software meant hitting most every limitation the platform had, and solving them.

Drupal 7 and the Limits of Procedural PHP

Drupal 7, released in January 2011, represents the high-water mark of procedural PHP architecture. Despite PHP 5.3 being available, Drupal 7 stayed mostly procedural to maintain backward compatibility, initially requiring only PHP 5.2.4.[22]

The render array tells you everything about where this approach breaks down. Drupal had been building forms out of nested arrays since the Form API arrived, and Drupal 7 applied the same idea to the entire page. Instead of objects with methods, Drupal 7 used massive, deeply nested associative arrays to define the HTML output of a page, and any module could alter the whole thing before it rendered.[23] A render array was a dynamic DOM implementation built entirely on hash tables:

$page['content']['system_main']['nodes']['body']['#prefix'] = '<div>';
$page['content']['system_main']['nodes']['body']['#suffix'] = '</div>';

There was no type safety. A typo in a key string, '#prfix' instead of '#prefix', would silently fail with nothing rendered and no error displayed. Debugging meant dumping megabytes of array data to the screen and scanning through it.

The memory cost was severe. A PHP 5 zval was 24 bytes on 64-bit systems (32 once the cycle collector's bookkeeping arrived in 5.3), and each one often pointed to another heap allocation.[24] Every level of every render array was another hash table full of them. Drupal 7 core needed a PHP memory_limit of 32MB just to install, and drupal.org's own documentation notes that 128MB or 256MB is typical in production.[22] In C, you could represent the same data structure in packed structs using kilobytes. The render array gave any module the ability to intercept and modify output before rendering, which was powerful and useful, but the cost was enormous.

The class registry was another artifact of this era. Before PSR-0 and later PSR-4 made standardized autoloading the norm, Drupal 7 scanned the files each module declared for class and interface declarations and stored their file paths in a database table. When you instantiated new MyClass(), the autoloader checked a cache and, failing that, queried the registry table to find which file contained it.[25] A C programmer would look at this and see a dynamic linker that needs a database round-trip to resolve a symbol. It worked in an ingeniously hacky way, but a filesystem map is a lot cheaper than a database query.

Every Request Starts Over

All of that work happened again on every uncached request. Drupal 7's index.php is only a few lines long: it calls drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL), then hands the request to the menu system,[26] so every uncached page paid for loading Drupal, walking its hooks, building its render arrays, and querying the database. Drupal's answer, from very early on, was to skip the work whenever it could. The page cache that first brought me to Drupal in 2002 promised to serve an entire page out of the database with a single query,[27] and by Drupal 7, trying to serve a cached page was the second of eight bootstrap phases, so an anonymous visitor could be handed a finished page and the request could exit before most of Drupal had loaded.[26]

That idea has held up well. When we profiled Drupal CMS 1.0 in early 2025, a cached anonymous request for the homepage took 29 milliseconds, made 7,178 function calls, and ran 4 database queries. With the page cache turned off it took 116 milliseconds, 34,711 function calls, and 27 queries, and WordPress 6.7, profiled the same way, took 183 milliseconds, 142,595 function calls, and 31 queries.[28] Our write-up credits Drupal's edge to its "very mature and multi-layered caching strategy and leaner bootstrap," and that bootstrap is the same part of the request Rasmus had profiled in 2007.

When traffic outruns the caches, those costs land on the database all at once. After the 2016 US election, the ACLU's action site (a Drupal site where supporters donate, join mailing lists, and write to elected officials) got so busy that database queries that normally took milliseconds took upwards of 10 seconds, or even more than a minute, and donors saw "503 Service Unavailable" errors.[29] The first fix, which we set up together with the ACLU team, took PHP out of the path entirely: a static HTML donation page served from a CDN (content delivery network, servers that hand out copies of a page without touching the site behind them) kept donations coming in while we worked on the site underneath. Then came query and index changes on the live database and better caching, which cut the initial page response time from 1400 milliseconds to 650. After that, load testing a replica of production found the next bottleneck, and the one after that, before real traffic did. All told, requests served per minute went up 705%, and on the night in December 2017 when the FCC repealed net neutrality, the site peaked at 1,900 submissions a minute without any downtime.[29]

Back in the Drupal 7 years, none of this slowed the community down. Drupal kept growing, and in 2011 Tag1 Publishing launched Drupal Watchdog, a full-color print magazine (more on that in Part 2).[30] But the procedural model was running out of room. It had carried Drupal a long way, and the render array was the system telling you it had hit its ceiling, so something had to change.

In Part 2, I'll talk about what changed, starting with the Java-fication of Drupal 8 and the engine-level work that saved PHP from itself, and ending with how Drupal came full circle. Along the way I'll share a couple more stories from Tag1's performance work, and where Drupal 11.3 and 11.4 have taken performance since.[31]

References

[1] Rasmus Lerdorf, "Performance and Security," OSCMS Summit, Sunnyvale, March 22, 2007; slide source.

[2] Dries Buytaert, "25 years of Drupal: what I've learned", January 2026 (Drupal 1.0.0 is dated January 15, 2001 in the Drupal CHANGELOG).

[3] The PHP Group, "History of PHP"; the PHP Foundation, "PHP turns 30".

[4] W3Techs, "Usage statistics of PHP as server-side programming language on websites" (PHP is used by 69.8% of the websites whose server-side language W3Techs can identify, September 29, 2026).

[5] "DriesNote Rotterdam: Drupal is light-years ahead of its reputation", drupal.org, September 29, 2026.

[6] Drupal 7 end of life; PHP supported versions.

[7] "History of PHP", php.net (Andi Gutmans and Zeev Suraski of Tel Aviv rewrote the parser for PHP 3.0, announced June 1998; their Zend Engine was "first introduced in mid 1999"; PHP 4.0 was "officially released in May 2000").

[8] Varonis, "PHP2Shell: One Upload, Full Server", October 2026 (CVE-2026-109618 and CVE-2026-109619; fixed in PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35, with no official fix for 8.1 or older).

[9] Nikita Popov, "PHP's new hashtable implementation", 2014.

[10] Sara Golemon, "Compiled Variables", May 2006.

[11] PHP 8.5 release announcement (November 20, 2025); PHP 8.6 release schedule; Partial Function Application RFC (implemented for 8.6).

[12] module_implements() and module_hook() in Drupal 6 and Drupal 7.

[13] Drupal issue #557542, "Cache module_implements()", opened August 24, 2009 (all from Nathaniel Catchpole: comment 30, "around 10% of page execution time"; comment 37, "about 1000 times on each page"; comment 38, "could increase by a factor of 10 or more," with the patch reducing module_hook() calls "from 783 to 58 on a completely empty front page" for "about 35ms saving").

[14] PHP 5.0.0 release, July 13, 2004; object handles.

[15] _field_ui_field_overview_form_validate_add_existing() in Drupal 7.

[16] Gregory Beaver, "Request for Comments: problems of namespaces and potential solutions", October 25, 2008; Lukas Smith, namespace separator decision, October 25, 2008.

[17] Drupal 6.0 release announcement, February 13, 2008.

[18] Drupal 7 CHANGELOG ("Fully rewritten database layer utilizing PHP 5's PDO abstraction layer").

[19] SA-CORE-2014-005, October 15, 2014; CVE-2014-3704.

[20] "Drupal powered NowPublic.com acquired by the Clarity Digital Group", drupal.org, September 2009; Dries Buytaert, "Examiner.com acquires NowPublic", September 3, 2009.

[21] Drupal 7.0 press release.

[22] Drupal 7.0 includes/bootstrap.inc (DRUPAL_MINIMUM_PHP 5.2.4, DRUPAL_MINIMUM_PHP_MEMORY_LIMIT 32M); Drupal 7 PHP requirements.

[23] "Render Arrays in Drupal 7".

[24] Nikita Popov, "Internal value representation in PHP 7, part 1", 2015.

[25] _registry_check_code() in Drupal 7.

[26] Drupal 7 index.php; Drupal 7 includes/bootstrap.inc (DRUPAL_BOOTSTRAP_PAGE_CACHE, "Second bootstrap phase: try to serve a cached page," and _drupal_bootstrap_page_cache(), which serves the cached page and exits).

[27] Jeremy Andrews, "My First Drupal Migration", Tag1, October 2023.

[28] Nathaniel Catchpole, "Drupal CMS 1.0 vs. WordPress 6.7: A Performance Showdown", Tag1, January 22, 2025 (XHProf wall times of 29,296, 115,849 and 182,643 microseconds, rounded here).

[29] Tag1 case study, "American Civil Liberties Union" (ACLU Action as one of the ACLU's Drupal sites, the November 2016 slowdowns and 503 errors, the static CDN donation page, the 1400 to 650 millisecond response time, the 705% increase in requests served per minute, and the December 2017 peak of 1,900 submissions per minute without downtime).

[30] Tag1, "Tag1 looks back on 20 years of Drupal experiences", February 16, 2021 ("Tag1 created Drupal Watchdog, the first print magazine dedicated to Drupal," with a "five year run from 2011-2016"); Tag1, "Issue #1 of a Drupal Magazine", January 7, 2011 (announced for distribution at DrupalCon Chicago); Drupal Watchdog, print advertisements ("Full-color advertisement sizes are available for Drupal Watchdog's print magazine").

[31] Drupal core blog, "Drupal 11.3.0: Biggest performance boost in a decade", December 17, 2025; "Drupal 11.4.0 is now available", drupal.org, July 1, 2026 (half the database queries of 11.3, and just over a third of the database and cache lookups of 11.0 on a cold cache).

Work With Tag1

Be in Capable Digital Hands

Gain confidence and clarity with expert guidance that turns complex technical decisions into clear, informed choices—without the uncertainty.