Generating 10,000 Test Addresses for Load Testing: A Walkthrough

Author:

Generating 10,000 Test Addresses for Load Testing: A Walkthrough

Introduction

When Daniel joined a small software development team, he was given an important assignment. His company was preparing to launch an online shopping platform, and the development team needed to find out whether the system could handle thousands of address submissions without slowing down.

The checkout form worked well when a few developers tested it manually. However, nobody knew what would happen when hundreds of users submitted delivery information at the same time. The team needed thousands of test records, a repeatable testing process, and a way to measure performance without exposing real customer information.

Daniel decided to generate 10,000 synthetic US address records and use them in a controlled load-testing exercise.

This approach allowed the team to test address forms, database operations, and application programming interfaces without manually entering every record. It also helped separate two important tasks: generating test data and measuring how an application behaves under load.

In this walkthrough, we will explore how to generate 10,000 test addresses, save them as JSON, and use them in a load-testing script. The examples use Node.js and Grafana k6, with clearly labelled synthetic data that must not be used for actual mail delivery.

Why Generate 10,000 Test Addresses?

Daniel initially wondered whether 100 records would be enough. His team explained that the purpose was not simply to fill a database. They wanted to examine how the application performed under different conditions.

Generating 10,000 test addresses can help developers evaluate several areas.

  • Database performance: Determine how quickly an application inserts, searches, updates, and retrieves address records.
  • API response times: Measure how long address submission requests take under increasing traffic.
  • Form validation: Check whether the application processes missing fields, long street names, apartment numbers, and invalid ZIP Code formats correctly.
  • Memory usage: Observe how the application behaves when processing large batches of records.
  • Error handling: Identify failures such as timeouts, duplicate records, rejected requests, and database connection problems.

However, 10,000 records do not automatically mean 10,000 concurrent users. The number of records describes the size of the dataset, while concurrent users and request rates describe the load placed on the system. Daniel needed to plan both separately.

Step 1: Decide What Each Test Address Should Contain

Before writing code, Daniel listed the fields his application expected. A typical US address form might contain a street address, apartment or unit, city, state, ZIP Code, and country.

For his initial load test, he chose the following structure:

{
  "id": 1,
  "street": "TEST STREET 00001",
  "unit": "APT 1",
  "city": "Austin",
  "state": "TX",
  "zip": "00000",
  "country": "US",
  "test_data": true
}

This is a synthetic testing record. The street name and ZIP Code are deliberately marked as test data, and the ZIP Code is a placeholder rather than a valid postal code.

The distinction matters because a record can look structurally correct without representing a real deliverable address. If Daniel’s goal were to test real postal validation, he would need a separate set of approved test fixtures or an appropriate address validation service.

For ordinary application load testing, synthetic records are useful because they can be generated consistently, repeated safely, and kept separate from production customer information.

Step 2: Set Up the Node.js Project

Daniel used Node.js because his team already worked with JavaScript. Node.js provides built-in file system tools that can write generated records to a local file without requiring an external address generation service.

First, he created a project directory and initialized it:

mkdir address-load-test
cd address-load-test
npm init -y

For this basic generator, no additional npm packages are required. The built-in fs module can write the output file, and crypto can provide a unique identifier if the team later needs one.

He then created a file named generate-addresses.js.

Step 3: Generate 10,000 Synthetic Address Records

Daniel wrote a script that generates 10,000 records using a collection of sample cities and state abbreviations. The street names are clearly labelled as test values, while the ZIP Code is intentionally a placeholder.

const fs = require("node:fs");

const TOTAL_ADDRESSES = 10000;

const locations = [
  { city: "Austin", state: "TX" },
  { city: "Denver", state: "CO" },
  { city: "Columbus", state: "OH" },
  { city: "Phoenix", state: "AZ" },
  { city: "Raleigh", state: "NC" },
  { city: "Salem", state: "OR" }
];

const addresses = [];

for (let i = 1; i <= TOTAL_ADDRESSES; i++) {
  const location = locations[
    (i - 1) % locations.length
  ];

  addresses.push({
    id: i,
    street: `TEST STREET ${String(i).padStart(5, "0")}`,
    unit: i % 3 === 0 ? `APT ${i}` : null,
    city: location.city,
    state: location.state,
    zip: "00000",
    country: "US",
    test_data: true
  });
}

fs.writeFileSync(
  "addresses.json",
  JSON.stringify(addresses, null, 2),
  "utf8"
);

console.log(
  `Generated ${addresses.length} synthetic test records.`
);

The script cycles through six city and state combinations and creates a unique test street label for every record. Every entry includes a numeric ID and a test_data flag.

The placeholder ZIP Code is intentional. This dataset is appropriate for testing data handling and request processing, but it should not be used to measure successful real-address verification.

Run the Generator

Save the script and execute:

node generate-addresses.js

The terminal should display:

Generated 10000 synthetic test records.

A file named addresses.json will appear in the project directory. It contains the generated records in JSON format, ready for use by test scripts or development tools.

For 10,000 records, building an array in memory is usually a reasonable starting point. If the dataset grows to millions of records, a streaming approach can reduce memory consumption.

Step 4: Verify the Dataset Before Testing

Daniel did not immediately send the generated records to his application. First, he checked that the file contained the expected number of entries and that the fields were present.

A quick verification script can help:

const fs = require("node:fs");

const addresses = JSON.parse(
  fs.readFileSync("addresses.json", "utf8")
);

const ids = new Set(addresses.map(item => item.id));

console.log("Total records:", addresses.length);
console.log("Unique IDs:", ids.size);
console.log(
  "All marked as test data:",
  addresses.every(item => item.test_data === true)
);

For the dataset to pass these basic checks, the total record count and unique ID count should both equal 10,000, and every record should be marked as test data.

Daniel also checked that the street, city, state, zip, and country fields existed. These checks would catch mistakes in the generator before they became confusing failures during the load test.

This step is important because a performance test is only useful when the input data is predictable and the test itself is working correctly.

Step 5: Install a Load Testing Tool

With the dataset ready, Daniel needed a way to send requests to his application and measure the results. He chose Grafana k6, an open-source load-testing tool that supports JavaScript-based test scripts and performance metrics. Its official documentation explains how to simulate virtual users, configure test duration, and check HTTP responses. <Cite refs={[“turn667910search0″,”turn667910search3”]}/>

Grafana k6 is maintained by Grafana Labs, an observability software company.

Grafana Labs address:

165 Broadway, 23rd Floor
New York, NY 10006
United States

The address is listed in the company’s official privacy policy. <Cite refs={[“turn732867search1”]}/>

Install k6 by following the instructions for your operating system in the official installation guide.

Before proceeding, Daniel confirmed that his team had permission to test the target application and that the test would run against a development or staging environment, not an unapproved production system.

Step 6: Create the Load Testing Script

Daniel wanted to submit all 10,000 records to a test endpoint and check whether the application accepted them. He created a file named load-test.js.

The example below assumes that the staging application provides a POST /api/addresses endpoint that accepts one address as JSON. Replace the sample URL with your own authorized staging endpoint.

import http from "k6/http";
import { check } from "k6";

const addresses = JSON.parse(
  open("./addresses.json")
);

export const options = {
  scenarios: {
    address_submission: {
      executor: "shared-iterations",
      vus: 20,
      iterations: 10000,
      maxDuration: "10m"
    }
  },
  thresholds: {
    http_req_failed: ["rate<0.01"],
    http_req_duration: ["p(95)<1000"]
  }
};

export default function () {
  const address =
    addresses[(__ITER) % addresses.length];

  const response = http.post(
    "https://staging.example.com/api/addresses",
    JSON.stringify(address),
    {
      headers: {
        "Content-Type": "application/json"
      }
    }
  );

  check(response, {
    "response is successful": (r) =>
      r.status >= 200 && r.status < 300
  });
}

This script uses 20 virtual users to distribute 10,000 iterations. Each iteration submits one record, and the data is selected from the generated file. Because each virtual user has its own iteration counter, records can repeat across virtual users. That is acceptable for a basic throughput test, but it is not a guarantee that all 10,000 unique records will be submitted exactly once.

The thresholds specify two example targets: fewer than 1% failed HTTP requests and a 95th-percentile response time below one second. These are sample targets, not universal performance standards. Daniel’s team would adjust them according to its actual service requirements.

The maxDuration setting also means the run can stop before all iterations finish if the workload takes too long. The team should check the final iteration count rather than assuming that all requests completed.

For more information, consult the k6 load testing documentation. <Cite refs={[“turn667910search5”]}/>

Step 7: Run the Load Test

Once the test script was ready, Daniel opened the terminal in the project directory and ran:

k6 run load-test.js

The tool began sending requests to the configured staging endpoint. It then reported metrics such as request duration, request failures, and completed iterations.

The most important measurements included:

  • Total requests: How many requests the application processed.
  • Failure rate: The proportion of requests that returned errors or failed.
  • Average response time: The average time required to receive a response.
  • 95th-percentile response time: The response time below which 95% of requests completed.
  • Throughput: The number of requests processed over time.

Daniel paid particular attention to the 95th-percentile response time. An average can look healthy even when a smaller group of requests takes much longer than expected.

The test also needed a correctly configured server endpoint. If the API rejected the synthetic ZIP Code or required authentication, the result would reflect validation or authorization failures rather than the system’s ability to handle realistic address submissions. The team therefore checked its API contract before interpreting performance results.

Step 8: Interpret the Results and Improve Performance

After the first run, Daniel compared the measurements with the application’s performance targets. He did not treat a successful test as proof that the entire system could handle every future traffic spike. Instead, he used the results to identify areas that deserved further investigation.

If response times increased sharply, the database might be spending too much time inserting records or checking constraints. If requests failed intermittently, the team might need to examine connection pools, application logs, memory usage, or rate limits.

If the server remained responsive but the load generator struggled, the testing machine or network connection might be the bottleneck. Grafana’s guidance recommends choosing a testing approach that matches the objective, since backend and browser-based tests measure different parts of the user experience. <Cite refs={[“turn667910search5”]}/>

Daniel also ran smaller tests before increasing concurrency. This made it easier to distinguish a problem caused by the dataset from one caused by the number of simultaneous requests.

Best Practices for Generating 10,000 Test Addresses

Before considering the work complete, Daniel followed several practical rules.

Keep test data separate from real customer data. Store generated records in development or staging environments and label them clearly.

Do not confuse format with validity. A street field and a ZIP Code field can have the expected format without identifying a real deliverable address. If postal accuracy is part of the test, use approved fixtures from the relevant provider or a separately verified dataset.

Avoid overwhelming external services. Run load tests only against systems you own or have explicit permission to test. Do not submit thousands of requests to a public address API without checking its terms, rate limits, and permitted testing practices.

Measure the whole workflow. A fast API response does not necessarily mean the database write, queue processing, or downstream operation has completed. Use the appropriate metrics and application logs to understand what happened.

Make tests repeatable. Save the generation script, test configuration, and performance results. Repeatable runs make it easier to compare application performance after code or infrastructure changes.

Conclusion

Generating 10,000 test addresses for load testing gives development teams a practical way to investigate how address forms, APIs, and databases behave under pressure. With Node.js, Daniel created a synthetic JSON dataset, verified its structure, and used Grafana k6 to submit requests to an authorized staging endpoint.

The walkthrough also highlighted an essential distinction: generating 10,000 records is not the same as simulating 10,000 concurrent users. A useful performance test defines both the dataset size and the intended workload, then measures response times, failures, and throughput against clear targets.

Start with safe synthetic data, verify the generator, and increase the load gradually. With a repeatable process and careful interpretation of results, your team can discover performance bottlenecks before they affect real customers.

Leave a Reply