C:\1990S\APACHEHT.TXT

1995

Apache: Somebody Had to Answer the Next Request

Apache's 1995 redesign combined reusable server processes with a modular request architecture, making shared maintenance and practical web service part of the same project.

7 min readCovers Apache's 1995 development and December 1 version 1.0Software That Shaped the 1990s

A web page can be popular for an awkward reason: everybody wants it at once. The document may be small, the server may know exactly where it is, and the computer may still spend a surprising amount of time waiting for visitors to finish their conversations.

Some clients are slow. Some requests involve more work than others. The server has to give each interaction enough attention without allowing one occupied worker to become the entire service.

Apache’s 1995 architecture addressed that problem with a collection of reusable processes and a modular way to handle requests. Its development addressed another version of the same problem: many administrators had useful fixes, and someone needed to organize them into a server other people could use.

The practical achievement was making separate pieces of work fit together. Websites needed that arrangement even when nobody visiting them knew the server’s name.

Shared fixes became a shared server

Apache’s project history describes the February 1995 effort to coordinate patches to NCSA httpd 1.3 after Rob McCool’s departure. Brian Behlendorf and Cliff Skolnick arranged the mailing list, shared space and developer accounts. Eight core contributors formed the initial group, with further contributions around them.1

The first official public release, 0.6.2, followed in April. Robert S. Thau’s Shambhala redesign introduced a modular API, resource pools and adaptive preforking; the group switched bases in July, released 0.8.8 in August and reached 1.0 on December 1. The Apache Software Foundation came later, in 1999.1

Those milestones distinguish the initial collection of improvements from a redesigned foundation for further improvements. Fixing a program and making it easier to extend are related tasks, but they don’t require the same changes.

Brian Behlendorf in 2007

Brian Behlendorf, August 5, 2007, much later than Apache’s initial development. Photo by Joi Ito, source and license record, CC BY 2.0. No alterations.

A coordinated release gives users a common point of reference. Without it, two administrators might both say they run the same server while actually relying on incompatible private patches. Each local improvement can be sensible; the collection can still be difficult to maintain.

This is one reason the organizational story belongs beside the architecture. A modular interface helps a contributor add behavior. A shared project helps other people discover, review and obtain that behavior. Neither benefit arrives automatically just because source code is visible.

Keep workers ready

The original Apache 1.0 source tag lets us inspect its standalone process model directly. The parent creates initial children and monitors their availability. Each child accepts a connection, reads and handles a request, closes its streams and loops. The code also regulates spare children and provides retirement conditions. It doesn’t create exactly one new process for every request.2

Preforking means creating worker processes before the individual request that needs them. The word describes the timing of process creation, rather than a special variety of HTTP.

The parent and children have different responsibilities. A worker deals with an interaction. The parent manages the collection of workers. Keeping those jobs distinct means the process responsible for maintaining capacity needn’t personally produce every response.

Here is an invented service-time model to make that arrangement concrete. It is deliberately simpler than Apache’s actual scheduling and networking. We fix the worker count, supply service durations, and assume requests are accepted in arrival order as soon as a worker becomes free.

Three requests arrive:

RequestArrivalWorker occupied for
A: slow transfer0 seconds8 seconds
B: small page1 second1 second
C: small page2 seconds1 second

The durations include whatever keeps a worker busy in our scenario. They aren’t CPU measurements, bandwidth calculations or observations from a 1995 Apache benchmark.

With one worker, A starts immediately and finishes at time 8. B has already waited seven seconds by then. It finishes at 9, and C finishes at 10 after also waiting seven seconds.

The waiting-time total is 14 seconds. Across three requests, the average is about 4.67 seconds. The small pages suffer because the one available worker remains occupied by A.

With two ready workers, A still occupies the first worker from 0 to 8. B uses the second from 1 to 2. C uses that same second worker from 2 to 3. All three requests begin on arrival, so their modeled queue waiting is zero.

One long interaction and two reusable workers

Original explanatory diagram, 2026. Invented arrivals and service durations, fixed two-worker capacity; not an Apache benchmark or a trace of its parent loop.

The second worker hasn’t made A’s connection faster. It has given the other requests somewhere to go. Separating those benefits is essential when interpreting a performance claim.

Concurrency isn’t unlimited capacity

The example can sound like an invitation to create more workers until every visitor is happy. Each worker consumes resources, however, and the machine still has shared limits.

Consider ten requests arriving simultaneously, each occupying a worker for five seconds. With two workers, two start immediately, two start at time 5, and the final pair starts at time 20. The waiting times are:

0, 0, 5, 5, 10, 10, 15, 15, 20, 20 seconds

Their average is 10 seconds, and all work finishes at time 25. The workers handle several successive requests; they don’t disappear after their first assignment in our model.

With five workers, five requests start at time 0 and five at time 5. Average waiting falls to 2.5 seconds, and the batch finishes at 10. That is a genuine improvement under the model’s assumptions.

The assumptions deserve their own sentence. We have treated each five-second duration as independent of how many other workers are active. Real disk contention, CPU contention or a shared network bottleneck can change that duration. If all workers compete for the same scarce resource, adding workers needn’t increase throughput in proportion to their number.

For a deliberately invented memory illustration, suppose each worker’s marginal cost is 3 MiB. Two cost 6 MiB; five cost 15 MiB. The additional concurrency costs another 9 MiB. These aren’t measurements of Apache 1.0’s footprint, and actual operating-system sharing can complicate the accounting. The arithmetic merely shows why capacity has a budget.

A server designer must therefore distinguish ready workers, occupied workers and the resources sustaining them. Counting processes is easy. Deciding how many remain useful takes evidence about the workload.

Adapting the collection

The 1.0 parent code checks for too few idle children and creates another when a slot and limit permit. Children can exit when there are enough idle servers or a configured request limit is reached.2

That is a feedback mechanism: observe availability, then adjust the collection. It is more nuanced than spawning a process whenever a packet arrives.

Our checker doesn’t reproduce that feedback loop. It models fixed capacity to isolate the effect of reusable workers. It also omits connection backlogs, timeouts, process creation costs, failures and operating-system accept behavior. The historical source supplies the architectural evidence; the modern model supplies transparent arithmetic.

That division makes the example more useful. A tiny simulation can test a narrow explanation without claiming to have reconstructed a complete web server. It can establish why a second independent worker helps B and C in the chosen scenario. It can’t establish a universal setting for a real installation.

There is another distinction inside “busy.” A worker can remain occupied while using little CPU. Waiting on another participant still prevents that worker from accepting a different interaction in this process model. Low processor utilization doesn’t automatically mean the service has enough available workers.

The slow visitor and the server can consequently disagree about where the delay lives. The visitor sees a download in progress; the administrator sees a worker unavailable. Both observations can be true.

Modules divide a different kind of work

A process model organizes concurrent execution. A module interface organizes behavior. They shouldn’t be drawn as if each module were another worker process.

Thau’s original 1.0 API notes describe request phases including name translation, authentication and access checks, type determination, response handling and logging. Handlers can accept a phase, decline it or return an error; the dispatch rules vary by phase. Resource pools group allocations for collective cleanup.3

An invented request for a museum’s exhibit page illustrates the distinction. One step identifies the requested object. Another checks whether access is allowed. Another determines the response type. A response handler sends content, and logging records what occurred.

Those are responsibilities within handling the request, rather than five simultaneous copies of the server. The same worker may pass through several responsibilities. Different modules can participate where their interfaces fit.

A useful extension doesn’t need to know everything about every other extension. It does need to honor the contract of its own phase. Declining to handle something and rejecting it with an error have different meanings: the first leaves room for another handler; the second can end normal processing.

This is also why extensibility doesn’t remove the need for careful design. Two modules can have individually understandable behavior while interacting badly through shared assumptions. Clear phases make the assumptions easier to locate and discuss; they don’t supply automatic agreement.

The architectural boundary is nevertheless valuable. It gives contributors a smaller place to make a change and reviewers a clearer question to ask: does this addition perform the promised responsibility without disrupting the surrounding arrangement?

Service is maintained work

Apache’s early development joined two forms of reuse. Worker processes could serve successive requests. A common codebase and module interface could support successive contributions.

Both required management. The server watched capacity; the project coordinated changes. Treating either collection as self-maintaining would have produced an optimistic plan with somebody else’s future cleanup hidden inside it.

The most satisfying result in the worked example is modest. Request A takes eight seconds either way. B and C become prompt because the service has another ready participant.

That is often what practical infrastructure accomplishes: it lets one slow interaction remain one slow interaction, while the next person still gets an answer.

Footnotes

  1. Apache HTTP Server Project, How Apache Came to Be, project history. Used for 1995 coordination, contributors, redesign and release chronology; Foundation formation separated as 1999. ↩ ↩2

  2. Original Apache 1.0 source tag, src/main/http_main.c, child_main, make_child and standalone_main. Actual loops inspected; the supplied-duration model doesn’t execute this code or predict installation performance. ↩ ↩2

  3. Robert S. Thau, Apache 1.0 API notes, basic concepts, handler outcomes and resource pools. Period interface distinguished from later Apache module/process systems; original examples don’t reproduce its code. ↩

software-historyapachehttpweb-serversinfrastructure