DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Career Development

There are several paths to starting a career in software development, including the more non-traditional routes that are now more accessible than ever. Whether you're interested in front-end, back-end, or full-stack development, we offer more than 10,000 resources that can help you grow your current career or *develop* a new one.

icon
Latest Premium Content
Trend Report
Developer Experience
Developer Experience
Refcard #399
Platform Engineering Essentials
Platform Engineering Essentials
Refcard #093
Lean Software Development
Lean Software Development

DZone's Featured Career Development Resources

Build Your Own Local AI QA Engineer With Docker, Ollama, LibreChat, and Playwright MCP

Build Your Own Local AI QA Engineer With Docker, Ollama, LibreChat, and Playwright MCP

By Faisal Khatri DZone Core CORE
Artificial intelligence is rapidly transforming software testing by enabling QA engineers to generate test cases and test plans, automate browser interactions, analyze and debug failures, and execute complex testing workflows using simple natural-language prompts. While cloud-based AI assistants offer impressive capabilities, they often require subscriptions and sharing potentially sensitive application data with third-party services. Running an AI-powered testing assistant locally addresses these concerns by providing better privacy, lower operating costs, and complete control over the testing environment. In this tutorial, we’ll learn how to build our own local AI QA engineer using Docker, Ollama, Qwen3:8b, LibreChat, and Playwright MCP. It will allow us to perform browser automation and interact with web applications using natural language, all without relying on cloud-based AI services. Understanding the Architecture Every interaction begins with the user. For example, a user enters a prompt in LibreChat, such as “Open the Playwright website and click the ‘Get Started’ button.” LibreChat serves as the conversational interface through which users interact with the AI assistant. Rather than processing the request itself, it forwards the prompt to a locally hosted large language model, Qwen3:8b, running via Ollama. After receiving the prompt, Qwen3:8b interprets the user’s intent and generates a step-by-step execution plan. Instead of interacting with the browser directly, the model determines which tools are required and communicates those instructions using the Model Context Protocol (MCP). These MCP requests are handled by the Playwright MCP Server, which acts as the bridge between the language model and the browser. It translates the AI-generated instructions into executable Playwright commands. The Playwright MCP Server then launches a Chrome browser and performs the requested actions. Depending on the prompt, it can navigate to websites, click buttons, complete forms, extract text from web pages, capture screenshots, and execute a wide range of browser automation tasks. Once the browser completes the requested operations, the execution results are returned to Qwen3:8b. The language model analyzes the browser output and transforms the technical details into a clear, human-readable response. LibreChat then presents this response to the user. Instead of displaying raw Playwright logs, it provides a concise summary such as: “Navigation completed successfully. The Playwright website was opened, and the Get Started button was clicked successfully.” This architecture enables browser automation through natural language while ensuring that every component runs locally. As a result, we benefit from enhanced privacy, greater security, and complete control over the entire AI-powered automation workflow. Prerequisites Before getting started, ensure that the following software is installed on your machine: DockerNode.js 20 or higher versionGitOllama We’ll use Docker Desktop to run LibreChat, Node.js to install and run the Playwright MCP Server, Git to clone the required repositories, and Ollama to download and serve the local large language model. Having these tools installed beforehand will make the setup process smooth and straightforward. System Requirements Running a local AI-powered browser automation stack requires a reasonably capable machine. A system with 16 GB of RAM or more is recommended to run Docker containers and the language model efficiently. We’ll also need 20–25 GB of available disk space, preferably on an SSD, to accommodate Docker images and downloaded models. While a dedicated GPU can significantly improve model inference speed, it is entirely optional, and the setup works well on modern CPUs. For this tutorial, I’m using the following configuration: Operating system: macOS (M2 Pro)Memory: 16 GB RAM We can have the same setup on Windows and Linux, with only minor platform-specific differences in the installation steps. Setting Up the Environment for the Local AI QA Engineer Docker, Node.js, and Git are widely used development tools, and detailed installation guides for each are readily available online. Installing Ollama To install Ollama, either download the installer from the official website or use the installation command provided for your operating system. For macOS, it can also be installed using the following Homebrew command: Plain Text brew install ollama Once the installation is complete, it can be verified by running the following command in the terminal: Plain Text ollama --version Installing Qwen3:8b Qwen3:8b is chosen for this setup because it offers a strong balance of reasoning, code generation, and performance, making it ideal for Playwright TypeScript test generation, AI agents, MCP integration, and modern QA automation workflows while running efficiently on a local machine. However, other higher models can also be chosen if you know a better one. Another factor in choosing this model was the available system memory. Since my machine has 16 GB of RAM, some memory also needs to be reserved for other tools used in this setup, such as Docker, LibreChat, and Playwright. We need to start Ollama first by running the following command from the terminal. (It should be kept running in the background): Plain Text ollama serve Open a new terminal and run the following command to pull the Qwen3:8b model: Plain Text ollama pull qwen3:8b It should take some time to complete the pull, as the model is around 5.2GB. Once the download completes, we can check the model by running the command: Plain Text ollama list It should list the model downloaded. Next, we can quickly verify by running the model using the command: Plain Text ollama run qwen3:8b Once the model starts, it will prompt you to enter a query. To verify that everything is working correctly, try a simple prompt such as “What is 2 + 2?”. Observe how the model processes the request and generates its response. If the setup is successful, it should return the correct answer, 4, confirming that the model has been downloaded, installed, and is functioning properly. To stop the model, type “/bye” in the prompt, and it should exit. Qwen3:8b provides a good balance between performance and resource usage, making it a suitable choice for this hardware configuration. If more RAM is available, you can opt for larger LLMs that offer stronger reasoning and coding capabilities. Installing LibreChat With Docker LibreChat is an open-source AI platform that provides a unified and customizable interface for interacting with multiple AI models. It enables us to manage all our AI conversations from a single application while supporting features such as AI agents, Model Context Protocol (MCP) servers, custom tools, and integrations with both local and cloud-based LLMs. LibreChat acts as the front-end chat interface that communicates with the locally running Qwen3:8b model through Ollama. It allows us to execute AI-powered browser automation workflows entirely on our local machine. Follow the steps below to install LibreChat: Step 1: Clone the LibreChat GitHub Repository The repository can be cloned by running the following command: Plain Text git clone https://github.com/danny-avila/LibreChat After cloning the repository, navigate to the LibreChat folder, copy the .env.example file, and create a new .env file from it. Plain Text cd LibreChat cp .env.example .env Let's keep the .env file as it is, using the default values. Step 2: Connect Ollama to LibreChat Ollama can be connected to LibreChat by updating its configuration in the “librechat.yaml” file. The example file is already available in the cloned repo. Run the following command to copy librechat.example.yaml and create librechat.yaml. Plain Text cp librechat.example.yaml librechat.yaml Update the following configuration in the file to connect Ollama to LibreChat: YAML endpoints: custom: - name: "Ollama" apiKey: "ollama" baseURL: "http://host.docker.internal:11434/v1" models: default: - "qwen3:8b" fetch: true titleConvo: true titleModel: "current_model" summarize: false summaryModel: "current_model" modelDisplayLabel: "Ollama" Make sure that this configuration is added to the “custom” block, which falls under the “endpoints” block. This configuration adds Ollama as a custom AI endpoint in LibreChat. The baseURL tells LibreChat where to connect to the Ollama API, while the default model specifies that Qwen3:8b should be used by default. Since LibreChat is running inside a Docker container while Ollama is running directly on the host machine, we use http://host.docker.internal:11434/v1 instead of localhost. The special hostname host.docker.internal allows the Docker container to access services running on the host system, enabling LibreChat to connect to the locally running Qwen3:8b model through Ollama. Setting fetch: true allows LibreChat to automatically detect and display all models available in Ollama. The remaining options configure the user interface by generating conversation titles using the current model, disabling conversation summarization, and displaying the endpoint with the label Ollama in the LibreChat interface. Step 3: Mount the Configuration in the docker-compose-override.yml The docker-compose-override.yml can be copied and created in the same way as we did “librechat.example.yaml”. Plain Text cp docker-compose.override.yml.example docker-compose.override.yml The following block should be updated in the docker-compose.override.yml file. YAML services: api: volumes: - ./librechat.yaml:/app/librechat.yaml This file mounts the custom “librechat.yaml” configuration file into the LibreChat container. By mapping ./librechat.yaml to /app/librechat.yaml, Docker ensures that LibreChat uses the custom configuration each time the container starts. This approach allows us to modify settings, such as custom endpoints and AI models, without rebuilding the Docker image. Step 4: Start the LibreChat Application Using Docker Compose The LibreChat application can be started using the following command: Plain Text docker compose up -d It will take some time for the Docker images to download, and containers will start. Run the following command from the terminal to check the Container status: Plain Text docker ps -a This command displays the status of all Docker containers. If any container is unhealthy or encounters an issue, its status will be clearly indicated in the output. In case any container is unhealthy or encounters an issue, the following command can be run to check its logs: Plain Text docker logs <container name> Once all the containers are started successfully, open a new browser and navigate to http://localhost:3080 to start LibreChat. Since we are accessing LibreChat for the first time, we will be prompted to register and create a new user account. After completing the registration process, we can sign in and start using the application. Step 5: Selecting Ollama > Qwen3:8b Model By default, the gpt-5.5 model is selected. To select the Qwen3:8b model: Click on the gpt-5.5 modelSelect Ollama > Qwen3:8b Once the Qwen3:8b model is selected, we can verify if it is working by sending a simple prompt such as “What is 2+2?” Make sure the command “ollama serve” is already running in the terminal in the background, else the model Qwen3:8b won't work on LibreChat. Once we receive a successful response from the model, we can confirm that the Qwen3:8b model has been configured and integrated successfully with LibreChat. Install Playwright MCP Server Playwright MCP can be installed by running the following command in the terminal: Plain Text npx @playwright/mcp@latest \ --host 0.0.0.0 \ --allowed-hosts "*" \ --port 8931 \ By default, Playwright MCP listens only on localhost, which means applications running inside Docker (like LibreChat) cannot connect to it. Using --host 0.0.0.0 makes the server accessible from Docker containers, while --allowed-hosys "*" allows requests from host.docker.internal instead of restricting access to localhost. Once the Playwright MCP server is started, we can leave it running in the terminal. After the Playwright MCP server starts, it shows the following message at the bottom: “For legacy SSE transport support, you can use the /sse endpoint instead”. We will configure the Playwright MCP server using the SSE (Server-Sent Events) transport. Although Playwright MCP also supports the Streamable HTTP transport, LibreChat currently does not support connecting to it via the /mcp endpoint. Therefore, the SSE transport is used to establish a reliable connection between LibreChat and the Playwright MCP server. Configure Playwright MCP Server in LibreChat Playwright MCP server can be added to LibreChat by updating the following configuration in the “librechat.yaml” file. YAML mcpServers: playwright: type: sse url: http://host.docker.internal:8931/sse timeout: 120000 This configuration registers the Playwright MCP server with LibreChat. The type: sse setting specifies that the connection uses the Server-Sent Events (SSE) transport, while the url points to the Playwright MCP server running on the host machine. The hostname host.docker.internal allows the LibreChat Docker container to communicate with services running outside the container. The timeout: 120000 sets the request timeout to 120 seconds, giving the AI agent sufficient time to complete browser automation tasks before the connection expires. However, the timeout can be extended to 15–20 minutes or more, as there is no harm in doing that. YAML mcpSettings: allowedDomains: - 'host.docker.internal:8931' - 'localhost:8931' The mcpSettings configuration also needs to be added under the ‘actions’ block in the “librechat.yaml” file. The mcpSettings.allowedDomains section defines the list of trusted MCP server endpoints that LibreChat is allowed to connect to. By including both host.docker.internal:8931 and localhost:8931, LibreChat can establish a secure connection to the Playwright MCP server, whether it is accessed from within the Docker container (host.docker.internal) or directly from the host machine (localhost). Any MCP server not included in this list will be blocked, providing an additional layer of security. Restart the LibreChat app so it reads the newly configured Playwright MCP server: Plain Text docker compose restart That, or we can also shut down the already running LibreChat and start it again by using the commands below: 1. To shut down LibreChat: Plain Text docker compose down 2. To start it again: Plain Text docker compose up -d After restarting LibreChat, log in and navigate to the home page, and follow the steps below: Click on the MCP Settings menu on the left-hand menu panel.In the MCP Settings window, click on the “+” button to add MCP. Fill in the details for adding the Playwright MCP server; make sure to add the following settings: MCP server URL: http://host.docker.internal:8931/sseTransport: SSEAuthentication: NoneTick the “I trust this application” checkbox. Click on the “Create” button to save the details. Make sure that the Playwright MCP server is started and running on the terminal as discussed in the earlier section Click Connect for the newly created MCP server to establish the connection and begin using it. If everything is fine, a message should be displayed on successful connection. Understanding Model Context Protocol (MCP) By itself, a large language model (LLM) is limited to generating text. It can answer questions, explain concepts, write code, or summarize information, but it cannot directly interact with external systems or perform real-world actions. Model Context Protocol (MCP) changes this by enabling AI models to communicate with external tools and services through a standardized interface. Instead of simply providing suggestions, an AI model can execute tasks such as interacting with browsers, reading files, querying databases, or creating pull requests. Think of MCP as USB for AI A simple way to understand MCP is by comparing it to the USB standard. Before USB became the universal standard, every hardware manufacturer used its own proprietary connector. Printers, keyboards, cameras, and other peripherals all required different cables and custom software integrations. This made connecting devices unnecessarily complicated. USB solved this problem by introducing a common communication standard. Once both the computer and the device supported USB, they could communicate regardless of the device type. Whether you connected a keyboard, webcam, microphone, or external hard drive, the same protocol handled the communication. MCP brings the same level of standardization to AI systems. Without MCP, every AI application requires building and maintaining custom integrations for every external tool it wants to use. If we switch to a different AI application, those integrations often need to be recreated from scratch, resulting in duplicated effort and increased maintenance. A collection of awesome servers for the Model Context Protocol can be found at mcpservers.org. With MCP, tools expose a common interface that any MCP-compatible AI application can use. The AI model only needs to understand the MCP protocol, while the implementation details are handled by the individual MCP servers. Why MCP Matters for QA Automation For QA Automation Engineers, MCP unlocks the ability to automate complete testing workflows rather than isolated tasks. Consider the following request: “Read the Jira story, generate Playwright tests, execute them, analyze any failures, and create a GitHub pull request.” With MCP, the AI agent can coordinate multiple tools to complete the entire workflow. For example, it can: Read the user story from JiraAccess the application’s source code from GitHubGenerate Playwright TypeScript testsExecute the tests in a real browserCapture screenshots, logs, and execution reportsCommit the generated tests to GitHubUpdate the Jira ticket with the test results Each of these actions may be handled by a different MCP server, such as a Jira MCP server, GitHub MCP server, and Playwright MCP server. From the AI model’s perspective, however, every server is accessed using the same standardized MCP protocol. This standardization is what makes MCP so powerful. Rather than building custom integrations for every tool, AI systems communicate through a single, consistent protocol. As a result, MCP servers for Playwright, GitHub, databases, and many other services can be integrated and used in a uniform, scalable manner, significantly simplifying the development of AI-powered automation workflows. Creating an AI Agent With Playwright MCP Server in LibreChat for Automation Testing Let’s create a new AI Agent for browser automation testing with Playwright MCP using the steps below: Step 1: Click on the Agent Builder menu on the left-hand menu panel. Step 2: Enter the following mandatory details to create a new agent: Name: Provide a meaningful name to the agent.Category: Provide a category to the agent.Model: Select Qwen3:8bMCP Servers: Click on the Add MCP Server Tools button > Select the Playwright MCP Server that we created in the earlier section.Click on the Save button. Step 3: Update the model parameters. Clicking on the Model field, which has Qwen3:8b selected, should open the Model Parameters page. The following parameters can be set using this page: Provider: OllamaModel: Qwen3:8bTemperature: 0.2Top P: 0.85Frequency Penalty: 0.00Presence Penalty: 0.00Reasoning Effort: MediumReasoning Summary: Auto Click on the Save button to set the parameters. Step 4: Setting the instructions for the AI agent. The Following instructions can be pasted into the Instructions field in the Agent Builder window, or a “SKILL.MD” file can be created and uploaded using the Skills section of this agent. Markdown # Skills for the Local AI Agent for automation testing You are an expert QA Automation Engineer controlling a browser through Playwright MCP. Your goal is to execute browser actions safely and reliably. ## Tool Usage Rules - Do not run all MCP tools at the same time - Use only one Playwright MCP tool at a time. - Wait for the result of each tool before deciding the next action. - Never assume the page state. - Inspect the current page before interacting. - Do not start the next MCP tool unless the first one is complete ## Navigation Rules Treat the following actions as navigation-triggering actions: - Clicking Login, Submit, Continue, Save, Next, Checkout, etc. - Clicking any hyperlink. - Form submission. - Any action that changes the URL or reloads the page. - Wait until the page is fully loaded before making another tool call. After any navigation-triggering action: 1. Do not call any DOM inspection tool immediately. 2. Wait until the page has completely loaded. 3. Wait for the URL to stabilize if it changes. 5. Continue only after the new page is available. 6. Never inspect the previous page after navigation. ## Rules for locating web elements - Take a fresh snapshot to inspect the current page - Do not use XPath locator strategy - Use the same field name to locate elements, do not hallucinate and add prefix or suffix to field names - Use Semantic locator strategy: getByRole, getByText, getByLabel, getByPlaceHolder, getByAltText, getByTitle, getByTestId - Never use brittle CSS selectors such as .btn-primary, .container > div:nth-child(2), #content div span, or auto-generated classes. - Avoid nth() unless there is no unique locator. ## Interaction Rules - Verify and confirm that an element exists before interacting. ## Error Recovery If any Playwright tool fails: - Stop issuing new actions. - Inspect the current page. - Check Interaction Rules - Determine whether navigation has occurred. - Retry only if the page state confirms it is safe. - Do not repeat the same action more than once without confirming that the page state has not changed. Never repeat the same click more than once without checking the current page. ## Important If a click causes navigation, always assume the previous execution context has been destroyed. Do not read the DOM until the new page has fully loaded and a fresh snapshot has been obtained. Show a summary of test execution with the step count and pass or fail status - Run only the steps that are provided; do not hallucinate - Any deviation from these rules is not acceptable - Do not generate any additional steps - Always prioritize stability over speed. Providing instructions to an AI agent helps define its behavior, responsibilities, and the boundaries within which it should operate. These instructions act as persistent guidance, ensuring the agent follows consistent practices every time it performs a task instead of relying solely on the user’s prompt. For detailed setup instructions and troubleshooting guidance, refer to the GitHub repository. With these steps, the local AI agent is now ready to take commands. Running the AI Agent for Browser Automation To start using the AI Agent, click on New Chat.Click on the model name dropdown and select My Agents > The name of the agent that you created. Let’s use the following simple prompt and see how it works. Plain Text open http://playwright.dev verify the page title Once the prompt is submitted, we can observe the browser as the AI agent begins executing the task. The agent invokes the Playwright MCP server, which automatically launches a browser and performs the requested actions to navigate to the website and interact with the page. After the task is completed, Qwen3:8b analyzes the outcome and returns the results directly in the LibreChat conversation, demonstrating browser automation powered by Playwright MCP and Qwen3:8b. Let’s run another prompt for a login test scenario: Plain Text Navigate to https://parabank.parasoft.com/parabank/index.htm Locate "Username" field using "name=username" Enter "john" into the "Username" field. Locate "Password" field using "name=password" Enter "demo" into the "Password" field. Locator "Log In" button using "input[type="submit"] Click on the "Log In" button Verify that the "Accounts Overview" page is displayed This prompt also takes some time to understand the request before execution begins. It is important to note that the clearer and more specific the prompt, the more efficiently the AI agent can interpret and execute it. Well-structured prompts reduce ambiguity, minimize the chances of hallucinations, and typically result in faster execution and more accurate outcomes. As a best practice, break complex tasks into clear, sequential instructions whenever possible to improve the agent’s reliability and overall performance. As shown in the screenshot above, the AI agent invoked five tools from the Playwright MCP server to interact with the application and complete the requested workflow. It navigated to the website, located the username and password fields, entered the provided credentials, and submitted the login form. Finally, it verified that the login was successful by confirming that the “Accounts Overview” page was displayed. Since this setup runs entirely on a local machine, the AI agent takes approximately one minute to begin execution and around 4–5 minutes to complete a simple scenario. For more complex scenarios involving multiple steps, validations, or integrations, the AI agent is expected to take longer to analyze the request and complete the execution. But Execution time can be significantly reduced by running the setup on a machine with more powerful hardware, such as additional RAM, a faster CPU, or a dedicated GPU. Watch the step-by-step YouTube tutorial for Building your Local AI QA Engineer. Final Words Building a local AI QA engineer with Docker, Ollama, LibreChat, and Playwright MCP is an excellent way to explore the future of AI-powered software testing while keeping complete control over the data and infrastructure. By running everything locally, we eliminate recurring API costs, improve data privacy, and create a flexible environment for experimenting with AI-assisted browser automation using natural language. This setup is only the beginning of what’s possible. As we become more familiar with MCP and AI agents, the local QA assistant can be extended by integrating tools such as GitHub, Jira, databases, or custom MCP servers to automate even more of the testing workflow. Happy AI-powered testing!! More
How to Design a Distributed Job Scheduler

How to Design a Distributed Job Scheduler

By Ajit Singh
Almost every backend eventually needs to run code on a schedule. Send the invoice at midnight. Retry the failed payment in five minutes. Generate the weekly report every Monday at 7 AM. Clean up expired sessions every hour. On one server, this is easy. You write a cron line and move on. The trouble starts when one server becomes ten. Now the same cron line lives on every box, so the invoice job fires ten times instead of once. Move the cron to a single “scheduler” box, and that box becomes a single point of failure. Every time you deploy new code, that process restarts, and if it crashes or the host dies, there is no second node to cover for it. Any job due during that downtime window silently never fires. A distributed job scheduler solves this. It runs jobs reliably across a fleet of machines, fires each job once even when nodes crash, and keeps working when parts of the system fail. This post walks through how to design one, the trade-offs at each step, and the mistakes that bite teams in production. What the Scheduler Has to Do Before drawing boxes, it helps to pin down the requirements. They split into two groups. Functional requirements: Run a job once at a specific time (a one-time job).Run a job on a repeating schedule, usually defined with cron (a recurring job).Support job dependencies, where job B runs only after job A succeeds.Retry a job automatically when it fails.Respect priority, so urgent jobs run before bulk jobs.Cancel or pause a job that is scheduled or already running. Non-functional requirements: Durability. Once the system accepts a job, it must not lose it, even if a node dies one second later.At-least-once execution. Every due job runs at least one time.Scale. The design should handle millions of jobs per day across many workers.Fault tolerance. A crashed worker must not block other jobs, and its work should be picked up by someone else. One requirement is worth calling out early. People often ask for “exactly-once” execution. In a distributed system, you cannot truly get it. What you can build is at-least-once delivery plus idempotent jobs, which together behave like exactly-once from the outside. More on that later. The Core Architecture The single most important idea in this design is to separate deciding when a job runs from actually running it. These are two different problems with different scaling needs, so they become two different components. A clean design has four parts: A scheduler that watches the clock and decides which jobs are due.A queue that holds ready-to-run jobs and hands them out.A pool of stateless workers that pull jobs and execute them.A datastore that holds job definitions and execution history, and acts as the source of truth. Why decouple the queue from the workers at all? Because load is bursty. At midnight, a thousand daily jobs may become due at the same second. If the scheduler called workers directly, that spike would hit them all at once. The queue absorbs the spike and lets workers drain it at a steady rate. It also lets you scale workers up and down without touching the scheduler. This is the same reason queues show up across system design, which I covered in detail in Role of Queues in System Design. Modeling Jobs in the Database The datastore is the source of truth, so the schema matters. A common approach uses two tables. One holds the recurring definition, the other holds individual runs. SQL CREATE TABLE jobs ( id BIGINT PRIMARY KEY, name TEXT NOT NULL, cron TEXT, -- null for one-time jobs payload JSONB, next_run_at TIMESTAMPTZ, -- when this job is next due enabled BOOLEAN DEFAULT TRUE ); CREATE TABLE job_runs ( id BIGINT PRIMARY KEY, -- unique id per run job_id BIGINT REFERENCES jobs(id), status TEXT NOT NULL, -- PENDING, RUNNING, SUCCEEDED, FAILED, DEAD attempt INT NOT NULL DEFAULT 1, scheduled_at TIMESTAMPTZ, started_at TIMESTAMPTZ, lease_until TIMESTAMPTZ ); CREATE INDEX idx_jobs_due ON jobs (next_run_at) WHERE enabled = TRUE; The partial index on next_run_at is the workhorse. The scheduler asks “which jobs are due now” many times per second, and this index keeps that query fast even with millions of rows. Each run moves through a small set of states. Drawing the state machine makes the retry and failure logic obvious. Defining Schedules With Cron Recurring jobs need a way to express “every day at 2:30 AM” or “every 15 minutes.” Cron is still the standard. A classic cron expression has five fields: Plain Text minute hour day-of-month month day-of-week 30 2 * * * -> 2:30 AM every day The Java world often uses Quartz cron, which adds a seconds field at the front and a year field at the end, giving six or seven fields. The two formats look similar but are not interchangeable, and mixing them up is a frequent source of jobs that never fire. The scheduler stores the cron string and computes a concrete next_run_at timestamp from it. After a run is enqueued, it computes the next one. This raises a real question: what happens if the scheduler was down for an hour and three runs were missed? This is the misfire problem. You generally pick one of two policies: Catch up. Run every missed occurrence in order. Correct for billing, expensive for everything else.Skip. Run only the next future occurrence and forget the missed ones. Right for jobs like cache refreshes where stale runs add no value. Make this an explicit setting per job. Teams that leave it implicit get surprised after the first outage. Picking Which Jobs to Run The scheduler needs to find due jobs and hand them off. There are three common ways to find them. Polling. Every second, query the database for jobs where next_run_at <= now(). Simple and reliable. The partial index keeps it cheap. The cost is a small delay, up to your poll interval.Timer wheel. Keep upcoming jobs in an in-memory structure sorted by time. Very precise and great for short delays, but you have to rebuild it from the database after a restart.Push. An external timing service fires an event when a job is due. Real-time, but now you depend on another moving part. For most systems, polling with a one-second interval is the right default. It is boring, and boring is good for a component you are trusting with billing runs. The harder problem is concurrency. If you run several scheduler instances for availability, they will all poll the same table at the same time. Without care, two of them pick the same job, and it runs twice. The clean fix in PostgreSQL is row locking with SKIP LOCKED: SQL SELECT id FROM jobs WHERE enabled = TRUE AND next_run_at <= now() ORDER BY next_run_at LIMIT 100 FOR UPDATE SKIP LOCKED; FOR UPDATE locks the rows this instance selects. SKIP LOCKED tells other instances to ignore locked rows and grab the next free ones instead. Many schedulers can now poll in parallel, each claiming a different batch, with no coordination service and no duplicate pickups. Airflow uses exactly this approach instead of a heavier consensus protocol, which is a good reminder that the simplest mechanism that meets the requirement usually wins. Why Exactly-Once Is a Myth Here is the scenario that breaks naive designs. A worker pulls a job, runs it successfully, and then crashes before it can tell the system “done.” The system still thinks the job is running. The lease expires, another worker picks it up, and the job runs a second time. You charged the card twice. You cannot delete this scenario. Networks drop messages and processes die at the worst moment. So you stop chasing exactly-once delivery and instead make the work safe to repeat. That means two things working together: At-least-once delivery. The system guarantees a due job runs at least one time, accepting that it may occasionally run more than once.Idempotent jobs. Running the same job twice has the same effect as running it once. The standard trick is an idempotency key built from stable identifiers, for example {job_id, run_id, attempt}, or a key tied to the business action like invoice_2026_06_charge. The worker records that key before committing side effects. If the same key shows up again, the worker sees the work is already done and acknowledges without repeating it. This is why each run gets its own unique id. A time-ordered id such as a Snowflake id or a ULID works well, because it is unique across the whole fleet without coordination and it sorts by creation time, which keeps the job_runs table naturally ordered. I explained the structure of these ids in How Snowflake IDs Work, and the deduplication pattern itself in Idempotent Receiver Pattern. There is one more subtle gap. The worker has to update the database and publish to the queue, and those are two systems. If it writes to the database and then dies before publishing, the job is lost. The transactional outbox pattern closes this gap by writing the job and an outbox row in one local transaction, then publishing from the outbox separately. I covered that in The Transactional Outbox Pattern. Coordinating at Scale A single scheduler instance has a throughput ceiling. Past a certain number of jobs per second, one process polling one database cannot keep up. There are two ways to grow. The first is leader election. You run several scheduler instances, but only one is active at a time. The others stand by and take over if the leader dies. A coordination service like etcd or ZooKeeper holds the leadership lock. This is simple to reason about, but the single active leader is still a throughput bottleneck. The second is sharding. You split the job space across many active schedulers. A simple scheme hashes the job id into one of N partitions, and each scheduler owns a set of partitions. Every job has exactly one owner, so there are no duplicate pickups, and throughput grows by adding schedulers. Consistent hashing makes it cheaper to add or remove schedulers without reshuffling everything. Sharding has one sharp edge. During a handover, while leases for a partition are changing hands, two schedulers can briefly believe they own the same partition. This is split brain. You do not try to make it impossible, because that is expensive. Instead, you let the worker-side idempotency check be the final safety net. If both schedulers enqueue the same run, the idempotency key means it still executes once. Google’s cron service takes a stricter route for its most sensitive launches. It writes the launch record to a quorum using Paxos before the job actually starts, so a failover cannot lose or double-fire it. For most teams, leases plus idempotency are enough, and full consensus is overkill. Detecting Failures and Recovering Workers crash. The scheduler has to notice and reassign their work, without stealing jobs from workers that are simply slow. The mechanism is a lease with a heartbeat. When a worker claims a run, it sets lease_until to a short time in the future, say 30 seconds. While the job runs, the worker periodically extends the lease. If the worker dies, it stops extending, the lease expires, and a recovery sweep moves the run back to PENDING so another worker can take it. SQL -- recovery sweep: reclaim runs whose lease has expired UPDATE job_runs SET status = 'PENDING' WHERE status = 'RUNNING' AND lease_until < now(); Two details make this robust. First, the lease timeout must be comfortably longer than a normal heartbeat interval, or a brief pause will cause a healthy job to be wrongly reclaimed. Second, you need protection against a zombie worker, one that froze on a long garbage collection pause, lost its lease, and then woke up and tried to finish writing results. A fencing token solves this. The reclaimed run gets a higher token, and the datastore rejects any write carrying an older token. I went deeper on time-bound ownership and fencing in The Lease Pattern in Distributed Systems. Retries Done Right A failed job should usually be retried, but retrying badly makes outages worse. If a downstream service is struggling and every failed job retries immediately, you pile on more load at the exact moment it can least handle it. The fix is exponential backoff with jitter. Each retry waits longer than the last, and a random jitter spreads the retries out so they do not all fire at the same instant. Plain Text attempt 1 fails -> wait ~1s attempt 2 fails -> wait ~2s attempt 3 fails -> wait ~4s attempt 4 fails -> wait ~8s (each wait randomized by +/- a few hundred ms) After a fixed number of attempts, stop. A job that keeps failing should not retry forever. Move it to a dead letter queue, a separate place for runs that exhausted their retries, and alert a human. The dead letter queue keeps a poisoned job from clogging the pipeline while preserving it for investigation. Operating the Thing A scheduler is infrastructure other teams depend on, so it has to be observable and controllable. For observability, track the metrics that tell you the system is healthy: Queue depth. A queue that keeps growing means workers cannot keep up.Scheduling lag, the gap between when a job was due and when it actually started.Run outcomes per minute, split by succeeded, failed, and dead.Lease reclaims, which spike when workers are crashing. For control, give operators real knobs. They should be able to pause a queue, drain a worker before a deploy so it finishes current jobs and takes no new ones, and replay a dead-lettered job after fixing the cause. Building these in from the start saves a lot of pain during the first incident. How Real Systems Approach This None of this is theoretical. The same building blocks show up across well-known tools, each making a different trade-off. Quartz. A mature Java scheduler. Multiple instances coordinate through a shared database using row locks, the same idea as the SKIP LOCKED approach above.Airflow. Orchestrates dependency graphs of tasks. Its scheduler uses database locks rather than a consensus protocol, favoring operational simplicity.Temporal. Models workflows as code and replays an append-only event history to recover state after a crash, which sidesteps a whole class of mid-task failure bugs.Celery. A popular task queue in Python, with a beat component that handles periodic scheduling.Kubernetes CronJobs. Run containerized jobs on a cron schedule inside a cluster, with configurable policies for missed runs and concurrency. See the Kubernetes CronJob docs.Google distributed cron. Writes launch state to a Paxos quorum before launching, so a leader failover never loses or doubles a run. The pattern across all of them is consistent. Decouple scheduling from execution, lean on the database or a quorum for coordination, accept at-least-once and make jobs idempotent, and design for failure as the normal case. Takeaways If you remember five things from this, make it these. Separate the decision of when a job runs from the work of running it. They scale differently.Do not chase exactly-once. Build at-least-once delivery and make every job idempotent.Use the database as a coordination primitive. SELECT ... FOR UPDATE SKIP LOCKED lets many schedulers poll safely.Use leases with heartbeats and fencing tokens to detect dead workers and reclaim their runs without double execution.Retry with exponential backoff and jitter, cap the attempts, and send the rest to a dead letter queue. A good scheduler is not clever. It is careful. It assumes nodes will die, messages will duplicate, and clocks will drift, and it keeps running anyway. More
Top 10 Best Places to Prepare for Your Next Data Engineer Interview
Top 10 Best Places to Prepare for Your Next Data Engineer Interview
By Rahul Han
Building an Idempotent Job Queue in Node. js That Never Runs the Same Task Twice
Building an Idempotent Job Queue in Node. js That Never Runs the Same Task Twice
By Bilal Azam
The 20 Software Engineering Laws
The 20 Software Engineering Laws
By Milan Milanovic DZone Core CORE
Why Your QA Engineer Should Be the Most Stubborn Person on the Team
Why Your QA Engineer Should Be the Most Stubborn Person on the Team

There is a common stereotype that software testing is just a dull exercise in checking what should already work. In reality, the cost of a missed bug in a serious product is far higher than a minor visual glitch or a button shifting out of place. It can lead to failures in critical workflows, data loss, service outages, and major financial damage for the business. People often say that QA is just there to check developers’ work. That is a superficial view. The role of QA is not simply to confirm that the code works, but to try to uncover every scenario in which it can fail. QA engineers have to think differently from the people who built the system. They need to think like real users, including those who will inevitably follow unexpected paths or use the product in ways no one originally planned. The myth that QA is routine work has lasted for years. Yes, regression testing is part of the job. But if you force an engineer into rigid checklist-driven testing, you will miss bugs. Strong QA does not follow a script mechanically. It explores the product, looks for edge cases, and finds new ways it can fail that no one considered during planning. QA Tasks and Approaches In most organizations, there is a distinction between QA (Quality Assurance) and QC (Quality Control), and both need to be considered together. It is not enough to verify the quality of the product being shipped. Teams also need to analyze the development and testing process itself, identify weaknesses, and look for ways to improve the product over time. Tasks break down into two major streams: Bugfix and regression. It is not enough to confirm in a ticketing system that a developer has fixed a bug. The goal is to make sure it does not come back a month later. That is why any critical fix should be covered by a regression test. This becomes a safeguard against product degradation.Feature testing. The work starts long before the build. Documentation and requirements are studied not just to understand how the feature is supposed to work, but to anticipate how it might break neighboring components. Depending on complexity, the right testing approach is chosen: from simple checklists to full test cases and automation where it makes sense. Good teams aim for high automation coverage, but they also stay realistic. Test coverage varies across modules: legacy code and experimental features require different approaches. In some areas, developer-owned unit tests may be enough. In others, QA needs to validate functionality through more complex integration scenarios. What Good QA Looks Like and How to Build It Good QA means stable tests that provide sufficiently complete coverage of a feature or product. It is also critical that QA and development teams actively collaborate. For example, when handing a product off to development, QA provides reports on what was tested and how, what risks remain, and what can be improved in coverage and overall product quality. It is also good practice to write tests alongside the code. For example, developers may write TAP-based functional tests for PostgreSQL or unit tests for Go code. At the same time, QA engineers prepare to test the finished product: they analyze documentation, talk to business analysts and technical product managers, and clarify expected behavior. Based on that information, QA specialists create test cases and checklists, automate them when appropriate, and add them to CI. It also helps to have a DevOps engineer who can take infrastructure work off QA’s plate. If there is no DevOps engineer, that responsibility often falls to the QA engineer as well. Manual vs. Automated Testing Automation is invaluable for repetitive tasks where manual re-checking leads to attention fatigue. In these cases, automated tests improve test quality and reduce errors caused by human fatigue or inattention. Exploratory manual testing complements automation. It helps uncover product behavior, edge cases, and unusual scenarios that automated tests may not cover. When building automated tests, test design techniques can be applied deliberately. For manual testing, the engineer's own experience matters most: you need to look at the product broadly and systematically, analyze what affects its behavior, and try to break it from angles nobody thought to consider. For example, one of my colleagues once tried to bring up a cluster in a nonstandard way by skipping one step, and the system failed. A user may follow their own path, and the developer simply may not have accounted for it. From an exploratory testing perspective, it is valuable to have custom tooling for fuzzing and generating random input combinations. For example, an in-house combinator can shuffle files with SQL queries to produce unexpected execution paths and input combinations. ASan is commonly used for detecting memory errors, while Valgrind is widely used for memory debugging, leak detection, and profiling. For web application testing, Selenium Grid supports cross-browser execution at scale. For automated tests, Pytest can serve as the test execution framework, while Allure Report generates CI-integrated reports from the test results. TestRail handles test case management and coverage tracking at the team level. Testing Against Open Source and Third-Party Components QA becomes more complex when a product is built on top of an open-source core or depends heavily on external components. In that case, the team is not testing only its own code. It is also testing how upstream changes, extensions, integrations, and internal modifications behave together. For example, a product based on PostgreSQL may include its own commercial logic while still pulling changes from the upstream project. That changes the QA process. If a bug is found in upstream code, quietly patching it only inside the commercial product is not always the right approach. In many cases, the issue should be reported back to the community, especially when the defect affects the original project and not only the commercial layer. The same logic applies to extensions and external components. Before adding a third-party component, QA should treat it as untrusted until it has proven stable in the target environment. That means exploratory testing, compatibility checks, regression coverage, review with SCA tools, and verification under realistic workloads. If bugs are found, they should be reported to the maintainers, and the component should be added only after the fix is available and the behavior is stable enough for production use. Upstream bugs are often harder to investigate than internal code defects. With internal development, the team usually has full visibility into every commit. If something breaks, tools such as git bisect can quickly point to the change that introduced the regression. With upstream code, changes often arrive in larger batches. When something fails after a merge, QA and developers may need to analyze a much larger set of external changes before they can isolate the root cause. Communication works differently, too. With internal code, the developer who owns the change may be one message away. In an open-source project or a third-party component, the feedback loop is longer. Reports need to be reproducible, technically precise, and useful to maintainers. This is where strong QA becomes more than bug reporting. It becomes engineering communication. AI and Vibe Coding: Hype or Genuine Value? Vibe coding is having a moment: you describe a task to an AI, and it writes the code for you. In QA, this can work, but with some important caveats. The most practical current use cases are generating sets of basic test cases (for example, pairwise combinations) and writing small helper scripts. This helps remove the “blank page” problem and saves engineers from some routine work. But it is important to understand the boundary. AI is like a junior engineer who never gets tired but often hallucinates. It cannot reliably anticipate deep architectural specifics of a product or subtle edge cases. At some point, a human must step in. Otherwise, the vibe can end with a critical bug in production. How to Become an Effective QA Engineer and Grow in the Profession At university, testing is typically covered as part of a programming curriculum. That is enough to learn the basics and understand whether the field interests you. After that, you can continue learning independently or take dedicated QA courses. For many serious QA roles, strong Python skills are one of the most useful foundations for automation, along with a solid understanding of Linux. This is especially true in systems development: you simply cannot test a complex product effectively if the terminal intimidates you or you do not understand how the environment works. But hard skills alone are only tools. The key soft skills for QA are systems thinking and the ability to make a clear technical argument. Finding a bug is not enough. You also need to explain why it should be fixed and make the case to the developer. This is not about avoiding conflict. It is about clear, constructive communication in a shared technical language. Where Can QA Engineers Grow? Technical track: QA Architect or Lead SDET. This path means continuous growth in coding, test architecture, and CI/CD. You become the expert who can build a quality engineering infrastructure from the ground up. Management track: Team Lead or Head of QA. Here, the focus shifts to processes, hiring, and mentoring. The idea that managers do not need deep technical knowledge is dangerous. To lead a team of engineers, you need to understand their pain points and the complexity of their work. And yes, some QA engineers eventually move into development or analysis. Testing experience becomes a real advantage there: engineers with a testing background tend to write cleaner code because they can see in advance where it is likely to break. Useful Resources A short list of resources to help you get started in QA and stay current with what matters: How Google Tests Software by James Whittaker, Jason Arbon, and Jeff Carollo: A useful book on how testing can be organized at scale, with strong ideas around risk, ownership, automation, and engineering culture. Lessons Learned in Software Testing by Cem Kaner, James Bach, and Bret Pettichord: A foundational text covering practical testing wisdom that holds up regardless of stack or methodology.ISTQB: Not every strong tester needs certification, but ISTQB is very useful for learning shared testing terminology, test design basics, and the difference between QA, QC, verification, validation, test levels, and test types. ISTQB materials are especially helpful for beginners who need structure. Ministry of Testing: One of the best-known global testing communities, with articles, discussions, courses, events, and practical material on quality engineering, exploratory testing, automation, and modern QA careers. Test Automation University: A practical resource for learning automation, API testing, UI testing, CI integration, and related engineering skills. Final Thoughts QA requires a lot of patience and focus, but knowing when to switch context matters just as much. Sometimes that is exactly what helps you solve a problem after spending a week chasing an intermittent failure with no clear result. A solution can almost always be found. Do not forget physical activity or anything else that lets your brain step away from the task for a while. It gives you the energy to come back and look at the problem from a different angle.

By Alex Vakulov DZone Core CORE
You Learned AI. So Why Are You Still Not Getting Hired?
You Learned AI. So Why Are You Still Not Getting Hired?

You learned prompt engineering. You built a chatbot. You finished a course. You added “GenAI” to your LinkedIn headline. And still, the interviews go nowhere. That does not always mean the AI job market is fake. More often, it means your signal is weak. Most candidates are showing that they can use AI tools. Employers are trying to hire people who can make AI useful inside a messy business, with real customers, bad data, edge cases, budgets, and risk. That is a very different standard. I have spent more than two decades working on systems where loose thinking becomes expensive very quickly. In enterprise platforms, weak architecture creates operational pain. In AI systems, weak judgment creates confident mistakes. That is why companies are not just hiring “people who know AI.” They are hiring people who can make AI dependable, measurable, and worth the cost. AI and big data top the list of fastest-growing skills. Source: World Economic Forum, “The Future of Jobs Report 2025.” The opportunity is real. But the winning profile is narrower than most people think. The Real Gap Is Not Learning. It Is Proof. A lot of job seekers think the market wants more certifications. It usually does not. What employers actually want is proof that you can take a vague business problem and turn it into a reliable AI workflow. That might mean improving support response quality. It might mean extracting fields from invoices. It might mean enriching product data. It might mean helping internal teams search better across thousands of documents. In every case, the question is the same: Can you turn AI from a cool demo into useful work? That is the hiring filter. Prompting Is Not the Skill. Precision Is. People still talk about prompt engineering as if it is a magic trick. It is not. The real skill is writing clear instructions for messy real-world work. Prompt engineering is the process of writing effective instructions for a model. Source: OpenAI, “Prompt engineering.” That sounds basic, but most candidates still operate at the level of vague intent. For example, “build a support bot” is not a serious instruction. A stronger version sounds like this: Handle password resets, order status checks, and return requests. Escalate angry customers. Do not invent policy. Log the reason for every escalation. Use only approved support content. That is not fancy. It is clear. And clear is valuable. This is one reason many smart people struggle to land AI jobs. They have learned how to ask AI interesting questions. They have not yet learned how to define work so precisely that a machine can do it safely and repeatably. That skill matters in AI engineering, AI product management, AI operations, AI consulting, and AI strategy. If you can define success clearly, you immediately become more hireable. Pretty Output Is Not the Same as Correct Work AI has a dangerous habit. It often sounds right before it is right. That is why evaluation matters so much. Evaluations (evals) are a way to test your AI system despite this variability. Source: OpenAI, “Evaluation best practices.” In plain English, this means one polished answer is not proof of quality. A summary can read well and still miss the legal risk. An invoice extractor can look accurate and still miss tax values. A product recommender can sound helpful and still suggest the wrong item. This is where many candidates lose credibility. They show outputs. They do not show checks. A stronger portfolio piece does not stop at “here is my AI app.” It says: Here is the task. Here is how I measured success. Here is where the system failed. Here is what I changed. Here is what still needs human review. That instantly feels more senior. If you want to stand out in AI hiring, start reviewing AI output as if your name is on it. Because in production, it will be. A Good AI Builder Can Break Work into Steps Another missing skill is decomposition. Can you take a big, fuzzy workflow and split it into smaller steps that AI can handle well? That is what real projects need. Take product catalog enrichment. A weak candidate says, “I built a product content generator.” A strong candidate says: First, classify the product. Then pull trusted attributes. Then draft the copy. Then check factual consistency. Then route uncertain cases to human review. That is a very different level of thinking. The same is true in support, search, compliance, reporting, and internal tooling. Employers are not paying for random prompt collections. They are paying for people who can structure work. And this is good news for job seekers. Because decomposition is not reserved for machine learning researchers. It is also a skill used by architects, product managers, QA leads, technical writers, analysts, and operations people. Many people are closer to AI work than they think. Trust Is Part of the Job Now There is another reason companies hesitate. Some AI tasks are easy to reverse. Others are not. A bad draft can be edited. A bad wire transfer cannot. A weak product description may annoy a customer. A wrong financial or medical recommendation can do real damage. That is why responsible deployment is now part of the skill set. Help manage the many risks of AI and promote trustworthy and responsible development and use of AI systems Source: NIST, “Artificial Intelligence Risk Management Framework (AI RMF 1.0).” The candidates who look strong in this market are the ones who naturally ask questions like: What is the cost of error? How often can this fail? Can we verify the result? Where should a human approve the outcome? What should the model never be allowed to do alone? This is not just a compliance issue. It is a product skill. It is an engineering skill. It is a leadership skill. And it is one of the clearest signals that someone understands production AI rather than just experimental AI. If the Economics Fail, the Idea Fails One more thing separates a hireable candidate from an enthusiastic learner. Business math. Not every workflow deserves the biggest model. Not every AI feature deserves to exist. Model choice and usage patterns directly affect cost, and official API pricing pages make that tradeoff visible across tiers and token categories. That means a serious AI professional should be able to say: This task is simple, so use a cheaper model. This task is high-stakes, so pay for a stronger one. This workflow runs at scale, so measure the cost before rollout. This use case saves time, but not enough money to justify production. That kind of thinking changes how hiring managers see you. Now you are not just someone who can build with AI. You are someone who can make a sound decision with AI. What Hiring Managers Actually Want to See If you have learned AI but have not landed a job, stop asking, “What course should I take next?” Start asking, “What evidence would make an employer trust me?” A strong answer usually includes one real workflow and five kinds of proof: A clear business problem. A precise task definition. A simple evaluation method. A sensible review process for risky cases. A rough explanation of cost and value. That is enough to make a portfolio piece feel real. Not flashy. Real. And real wins. The AI job market is not only looking for people who can talk to models. It is looking for people who can think clearly, reduce ambiguity, catch mistakes early, and connect technical work to business outcomes. That is why many candidates feel stuck. They are training for “using AI.” Employers are hiring for judgment. Once you understand that, the path changes. Build fewer demos. Show more decision-making. That is how you stop looking like someone chasing AI jobs, and start looking like someone ready to do one. For more practical insights on AI careers, software architecture, and building production-ready systems, connect with Faisal Feroz on LinkedIn and read more on his blog.

By Faisal Feroz
Stop Using the ATM-Didn’t-Kill-Jobs Story to Reassure Developers About AI
Stop Using the ATM-Didn’t-Kill-Jobs Story to Reassure Developers About AI

The ATM Didn't Kill Bank Tellers' Jobs There's a story economists love to tell about ATMs and bank tellers. You've probably heard it. When ATMs were introduced in the 1970s, everyone predicted they would eliminate teller jobs. They didn't. By the 2000s, there were actually more tellers than before the ATM existed. The story became a load-bearing parable for anyone who wanted to argue that technology doesn't kill jobs, cited by economists like Daron Acemoglu and David Autor, by tech executives like Eric Schmidt, and more recently by politicians reaching for reassuring historical analogies when asked about AI. David Oks recently published a sharp piece that complicates this parable. His conclusion: ATMs didn't kill bank tellers, but the iPhone did. Teller employment entered prolonged decline in the 2010s because mobile banking made the branch itself irrelevant. Once customers stopped coming in, the institutional context that gave the teller role its value simply ceased to exist. Oks draws a clean distinction from this: it is paradigm replacement, not task automation, that actually displaces workers. The ATM tried to fit capital into a labor-shaped hole. The iPhone changed the shape of the hole entirely. It's a compelling argument. And for software developers watching coding agents write functions, generate tests, and draft pull requests, it offers a certain comfort: you're not being replaced, just assisted. The real disruption, if it comes, will look completely different. I think that comfort is premature. Oks argument in itself is not wrong, but it is incomplete and the part it leave out matters a lot. The iPhone Argument Has a Gap Oks is right that the iPhone, not the ATM, killed the bank teller job. But his broader thesis that paradigm replacement is the mechanism of displacement, not task automation, isn’t historically accurate. Manual weavers weren't displaced by a paradigm shift. The power loom did the same thing they did, faster and cheaper, and they became redundant. The technology simply fit itself into a labor-shaped hole and the labor disappeared. Scribes, typing pools, telephone switchboard operators, elevator operators, etc. none of these occupations required a paradigm shift to be eliminated. In each case, task automation was sufficient. The job existed because a specific set of tasks had economic value. When those tasks could be performed more cheaply by a machine, the role contracted and eventually disappeared. What Oks has actually identified is a specific condition under which task automation fails to eliminate a role. That condition has two components: the automated task must be embedded in a broader human interaction that has independent value, and the cost reduction from automation must enable expansion of the overall activity. Both conditions held for bank tellers. The teller's residual tasks such as relationship management, complex service interactions and cross-selling financial products were genuinely valuable and hard to automate. And cheaper branches meant more branches, which meant more tellers. Demand expanded to absorb the efficiency gains. When those conditions don't hold, task automation alone is sufficient to eliminate a profession. The question for software development is which set of conditions applies. Where Software Development Sits Right Now At this moment, the conditions for software developers look more like bank tellers than manual weavers. But only just, and the gap is closing. Current coding agents are genuinely capable at specific set of tasks: writing boilerplate, generating tests, documenting code, drafting pull requests, handling well-specified implementation problems. These are real and substantial parts of software work. But they remain components of something larger. The tasks that define senior software development include understanding what problem is actually worth solving, navigating the organizational and technical tradeoffs in an architectural decision, holding the accumulated context of a system's history and making decisions about about second-order consequences. Current AI tools handle poorly these activities. This is why the most experienced engineers are the heaviest users of these tools. Staff+ engineers, according to recent survey data from The Pragmatic Engineer, use AI agents at higher rates than any other level. They're not threatened by the tools; they're amplified by them. Their judgment is the thing the tool can't replace, and the tool makes their judgment more productive. In Oks' terms: the automated task is still a component of a richer service relationship, and the remaining human tasks still have genuine value that can’t be easily automated. There's a second reason the current moment feels stable, and it's more structural. Almost everything about how software development is organized today was designed for humans working at human speed. The way we gather and surface production issues. The way we structure pull request reviews. The way we run sprint planning, write tickets, conduct incident postmortems. These workflows were built around human cognitive limitations and human communication patterns. Dropping a coding agent into them is like replacing a factory's steam engine with a single large electric motor and keeping the drive shaft: you get some efficiency gains, but you're not anywhere near the real potential of the technology. This means we're still in early transition. The friction isn't just technical capability; it's organizational inertia. And as long as workflows are designed for humans, the human remains load-bearing in ways that aren't purely about cognitive ability. Why the Trajectory Matters More Than the Current State Here's where the bank teller analogy breaks down in a way that should concern developers. ATMs had a hard ceiling. They could handle cash withdrawals, deposits, and balance checks. They couldn't have a conversation, make a judgment call, or handle anything outside their programmed transaction set. The teller's residual role was structurally protected by what the machine couldn't do. AI coding tools don't have an obvious ceiling. The tasks they handled poorly six months ago they handle better today. The tasks that seem safely human (system design, architectural judgment, understanding business context) are precisely what the next generation of tools is being explicitly built to address. The boundary between "what AI can do" and "what requires human judgment" is moving. The debugging agent is a useful case study here, because it illustrates something more significant than incremental improvement. Traditional observability and debugging workflows were designed around humans: a log file, a Slack thread, an on-call engineer piecing together a causal chain under pressure at 2am. The first generation of AI debugging tools tried to fit into that workflow: read the logs, suggest a fix, slot into the existing process. What's emerging now is different. Debugging agents designed from the ground up to collect data in machine-readable formats, pre-correlate signals before a human ever sees them, deduplicate noise, and surface a ranked hypothesis rather than a raw stream of events. This is not task automation slotted into an existing workflow. This is the workflow being redesigned around what machines need rather than what humans can do. It's the first concrete step toward something that would have sounded like science fiction three years ago: systems that close the loop on their own failures, with humans in an oversight role rather than a diagnostic one. The human's job shifts from "figure out what broke and why" to "decide which failure modes matter and whether the agent's response is appropriate." That's a real and valuable role. It's also a much smaller one. This pattern will repeat. PR reviews are being rethought not as human checkpoints with AI assistance, but as automated verification layers with human escalation paths. Architectural decision-making is starting to be approached with tools that can compare the tradeoffs made across a codebase. The demand elasticity argument (there's always more software to build, so developers will always be needed) deserves a direct response, because it's the most common counterargument and the most misleading. It's true that the demand for software is enormous and largely unsatisfied. New markets are opening: small businesses that couldn't afford custom software at previous price points now can. There is genuinely more to build than we're currently building. But new demand mostly benefits new categories of developers serving new markets: the solo developer building custom tools for local businesses, the non-specialist who can now direct agents without deep technical knowledge. It doesn't necessarily sustain employment at current levels in the enterprise and product development tiers where most professional developers currently work. When a team of five engineers with AI agents can do the work that previously required fifty, the question is whether organizations respond by building ten times as much software or by not rehiring the other forty-five. The historical pattern in most industries, including banking after the iPhone, is the latter, at least in the short to medium term. What This Means The structural conditions for developer employment look reasonably healthy for the next several years. The remaining human tasks are real and valuable. The workflows haven't been redesigned yet. The organizational inertia is substantial. But the trajectory is not ambiguous. The tasks that AI handles poorly today are the explicit targets of the tools being built right now. The workflows are starting to be redesigned from the ground up rather than retrofitted. The "richer service relationship" that protects the developer's role is narrowing as the agent takes on more of what previously required human judgment. The compression threat is more immediate and more certain than the paradigm-replacement scenario, but both are real. In the near term, the result will be the same output from smaller teams. In the longer term, "software development" as a distinct full-time profession organized around the ability to write and make decisions about code may dissolve into something else: a capability that technical knowledge workers exercise incidentally, the way managers today use Excel without being spreadsheet professionals. Oks ends his piece with a distinction that's worth noting: the ATM substituted tasks, the iPhone made them irrelevant. The people reassuring developers by citing the ATM story may not have noticed that we are already in the iPhone moment. Change in technology-driven labor markets follows a familiar pattern. It happens gradually, incremental improvements, partial automation, roles that adapt and absorb, and then all at once, when the paradigm finally shifts and the institutional context that made a role economically legible simply ceases to exist. We're in the gradual part. The "all at once" is not scheduled, but it's on the roadmap.

By Thomas Johnson DZone Core CORE
AI Didn't Replace Seniors; It Just Made Them the Bottleneck
AI Didn't Replace Seniors; It Just Made Them the Bottleneck

Two to three years ago, the narrative surrounding AI in software engineering was quite simple: Inevitably, LLMs and AI tools would improve so much that they would replace most of the engineering workforce, including senior engineers. LLMs would write production-grade code, and organizations would need little more than a couple of product owners, a carefully crafted prompt, and a deploy button. Entire conferences were held around this concept, LinkedIn and Tech YouTube were drowning in "doomsday" posts. This prediction aged quite interestingly. What actually happened was both surprising and uncomfortable. Yes, AI tools can generate fairly good code at an extraordinarily fast pace, but the bottleneck in software delivery did not disappear. It shifted onto the shoulders of senior engineers, and the compounding effects of that shift are only now becoming visible. The Bottleneck Moved Here's the uncomfortable arithmetic of AI-assisted development in 2026. AI coding tools now write roughly 41% of all new commercial code, according to recent industry analyses. Feature velocity is at an all-time high, and yet experienced developers report spending more time debugging, more time in code review, and more time untangling architectural decisions that no human actually made. A 2025 study by METR found a striking disconnect: Developers using AI tools felt approximately 20% faster, but their measured task completion time was actually 19% slower on real-world codebases. The gap between perception and reality is nearly 40 percentage points. Two cognitive biases explain this: automation bias (where we overtrust automated output) and the effort heuristic (where less typing feels like less work). The code generation part was never really the hard part. Understanding what you have built, why it behaves the way it does, and what will break when you change it — that was always the bottleneck. AI made the fast part faster, but it also made the slow parts dramatically slower. Cognitive Debt: The Invoice Tech Companies Are Ramping Up There is a term gaining traction in engineering circles that captures this phenomenon precisely: cognitive debt. Unlike technical debt, which lives in the codebase and can be measured with linters and static analysis, cognitive debt lives in the minds of the developers working on the system. It is the growing gap between the amount of code that exists and the amount any human genuinely understands. Addy Osmani described this as comprehension debt: the hidden cost that does not show up in velocity metrics. The codebase looks clean, tests are green, and PR counts are up, but underneath those reassuring dashboards, parts of your system are running in production that no one on the team can explain. Either because that code was written by a non-human, or because the human that written it got laid off and replaced by an LLM. To be totally fair, this phenomenon existed even in the "pre-AI era." I have seen this firsthand in large enterprise environments — some microservices deployed to production where, due to team restructuring and layoffs, the people who developed the code are gone, and the people left maintaining it never built a mental model of how it works. The current tools are making this far worse. The organizational assumption that reviewed code is understood code no longer holds- engineers are approving code they did not fully understand, and that approval now carries implicit endorsement, thus quietly distributing liability. Margaret-Anne Storey, whose February 2026 research helped popularize the term, put it plainly: A program is not its source code. A program is a theory that lives in the minds of the developers. When AI generates the implementation and humans merely review it, that theory can fragment or disappear entirely. And when it does, even simple changes become dangerous. The Review Bottleneck Is a Senior Engineer Bottleneck So, who is left holding the system’s mental model together? The senior engineers. The ones who remember why that architectural decision was made under pressure eight months ago, who can look at a diff and immediately know which behaviors are load-bearing and which are cosmetic, who can tell the difference between a safe refactor and one that will quietly shift something users depend on. These engineers are now the scarce resource the entire organization depends on, and here is the irony: the same wave of AI adoption that increased the volume of code requiring review also triggered the layoffs that thinned the ranks of the people capable of reviewing it. Between 2023 and early 2026, the tech industry shed hundreds of thousands of jobs. Over 245,000 globally in 2025 alone, with 2026 on pace to exceed that figure. But the layoffs were not evenly distributed across seniority levels. Companies cut junior and mid-level roles aggressively, believing AI tools could absorb the work. The remaining senior engineers did not get a lighter workload. They got a heavier one, with fewer people to delegate to. This is what burnout looks like in 2026 — a slow erosion. Engineers who stop pushing back in design reviews because they do not have the energy, code reviews that become rubber stamps, and architectural choices made by default rather than deliberation. The people most likely to burn out are the people hardest to replace. And it gets worse.... The Broken Pipeline: Where Are Tomorrow’s Seniors Coming From? This brings us to the part of the story that keeps me up at night. If the current senior engineers are overwhelmed and burning out, or worse, laid off, the natural question is: who replaces them? The answer, increasingly, is nobody. Entry-level developer hiring has collapsed. Ravio’s 2025 Tech Job Market Report found that entry-level hiring dropped 73% year over year, while overall hiring dipped only 7%. This is a deliberate strategic decision playing out across the industry. In 2019, new graduates represented 32% of Big Tech hires. By 2026, that number has cratered to roughly 7%. The pipeline narrows invisibly. The reasoning from a CFO’s perspective is straightforward: why pay a junior developer $80–100K plus six months of ramp-up when a senior engineer with AI tools can cover triple the output? The math makes sense on a quarterly earnings call; it is catastrophic on a five-year horizon. Schools do not (yet) produce senior engineers - experience does. Debugging someone else’s code teaches you how systems fail, while writing boilerplate teaches you how systems are structured. Reviewing pull requests, even if stressful at first, teaches you how other people think about problems. Every one of those learning opportunities is a task that AI now handles, or that simply does not happen because there is no junior on the team to do it. A significant reduction in junior hiring between 2024 and 2026 means a proportional reduction in candidates for senior roles between 2031 and 2036. The industry is eating its seed corn. The Easy Button and the Erosion of Sharpness There is another dimension to this that extends beyond organizational hiring strategy and into individual skill development. When a tool exists that can do your job (at least the visible, measurable parts of it) for you, people will use it. It is human nature to be lazy. But consider what happens at the individual level. A mid-level engineer who has leaned heavily on AI code generation for two years stops building the neural pathways that come from working through problems manually. They lose the 30 seconds of working memory where they would have wired together the algorithm, considered edge cases, and built a mental anchor for that pattern. Multiply that across hundreds of completions per week, and the atrophy becomes significant. Luca Rossi describes two cognitive modes that matter here: create mode, where you actively build mental connections between ideas, and review mode, where you assess existing work with lower cognitive engagement. AI tools push developers from create mode into review mode by default. You stop solving problems and start evaluating solutions someone else produced. Review mode feels productive - you are reading code, spotting issues, making edits, but you are not building the mental model that lets you reason about the system independently. All of this is happening while the bar for what it means to be a qualified engineer is rising. FAANG interviews in 2026 have shifted from pure algorithmic puzzles toward scenario analysis, debugging exercises, and system reasoning under realistic constraints. Companies are looking for signals that cannot be autocompleted: the ability to read logs, investigate a performance regression, and explain why a request path suddenly slows under load. The interview process is selecting for exactly the kind of deep understanding that routine AI-assisted work erodes. The paradox is stark. The tool that was supposed to democratize software engineering is simultaneously making it easier to produce code and harder to develop the judgment needed to produce it well. A Humble Prediction: The Market Will Bifurcate If current trends continue (and every indicator suggests they will accelerate), the software engineering talent market is heading toward a painful bifurcation. On one side, a shrinking pool of senior engineers with genuine system understanding, architectural judgment, and the ability to reason about code they did not write. These people will command premium compensation and face relentless demand. They will also face relentless cognitive load because the organizational layers that used to absorb complexity beneath them are gone. On the other side, a growing population of developers who entered the profession during the AI era, who are proficient at prompting and reviewing but who never built the foundational mental models that come from years of hands-on struggle with real systems. They will be productive in narrow contexts and fragile in novel ones. They will pass AI-assisted coding assessments and struggle in incident rooms. The gap between these two groups will widen because nothing in the current incentive structure encourages closing it. What Can Be Done I do not pretend to have a complete playbook for this. But I believe the conversation needs to start in a few specific places. First, engineering organizations need to stop measuring AI adoption purely through velocity metrics. If your team is shipping 40% more code but your senior engineers are rubber-stamping reviews because they are overwhelmed, you have not improved; you have accumulated invisible debt that will come due at the worst possible moment.Second, the industry needs to redefine what a junior developer role looks like in 2026. The entry-level work is no longer writing boilerplate, but reviewing AI output, testing edge cases, writing better prompts, and building the judgment that AI cannot provide. The junior developer of 2026 looks different from the one we hired in 2018, and our job descriptions, onboarding, and expectations need to reflect that. But the role MUST exist. Eliminating it is organizational and market-wide amnesia in the long run.Third, individual engineers (at all levels) need to be honest with themselves about whether their daily workflow is building understanding or just building output. If you cannot explain a function to a colleague without referencing the prompt that generated it, you do not understand it well enough to own it, and that gap will inevitably catch up to you. AI tools are revolutionizing the software development space, but contrary to popular belief, AI did not replace senior engineers; it made them irreplaceable, overloaded, and increasingly alone. Organizations and engineers need to be aware of the hidden effects AI-assisted coding has and adapt accordingly.

By Abgar Simonean
Cost Is an SLI: Why Your System Is “Healthy” but Burning Cash
Cost Is an SLI: Why Your System Is “Healthy” but Burning Cash

There's a class of failure that doesn't page anyone. No SLO breaches, no latency spikes, no 3 AM Slack messages from an on-call engineer clutching cold coffee. The system is working — by every conventional measure it's healthy — and yet something is deeply wrong. Money is hemorrhaging out of the infrastructure at a rate that won't become visible until the CFO opens a billing dashboard, squints at a number that seems obviously misformatted, and then realizes with a specific, cold dread that it isn't. This is what runaway cloud spend actually feels like from the inside. Not an explosion. A slow bleed mistaken for normal circulation. I've watched this happen to teams that were, by all accounts, technically sophisticated. Engineers who could discourse fluently on consensus algorithms and distributed tracing, who had meticulous runbooks and well-tended Grafana boards — and who had absolutely no instrumentation on what their systems cost per request. The money question was someone else's problem. Finance's problem. The CFO's problem. Right up until it became everyone's problem simultaneously, in a conference room, with a spreadsheet nobody had any good answers for. The SaaS startup whose AWS bill doubled to $500,000 in a single month didn't have a cloud problem. They had an instrumentation problem wearing a cloud problem's clothing. Orphaned virtual machines — instances spun up for a load test, or a one-off migration, or some experiment that concluded months ago — sitting there, billing hourly, invisible because nobody had tagged them to a team or a project or a cost center. Reserved-instance coverage that looked adequate in aggregate but had grown misaligned with the actual workload topology. The machines doing real work were on-demand; the reservations were funding a ghost fleet. This isn't exotic negligence. It's the default state of systems that grow faster than their accounting practices. The $2.4 million cloud bill where 80% of charges were data egress — that one is almost elegant in how completely it exposes a conceptual failure. Egress fees are the tollbooth you forget exists until you've already driven through it ten thousand times. Architects design for compute. They think in CPUs and memory and IOPS. Network transfer is ambient, assumed-cheap, treated as infrastructure rather than metered consumption. But cloud providers have always made money on the exits. Data flowing inward is free or nearly so; data flowing outward is where the revenue hides. A system that fetches large payloads, transforms them, and then ships the results to another region or a third-party analytics endpoint can accumulate egress charges that dwarf its compute costs — and nothing in the default monitoring stack will tell you this is happening until the invoice arrives. The deeper pathology here is architectural, and it predates cloud computing entirely. Distributed systems were designed by people who had to fight for every byte of memory and every millisecond of CPU time. Scarcity was the operating assumption. The engineering culture that emerged from that constraint treated resource efficiency as a first-order concern — you measured it, you optimized it, you were embarrassed when your code was wasteful. Then the cloud arrived with its promise of elasticity, its pay-as-you-go rhetoric, its infinite-seeming provisioning capacity, and something in the collective engineering psyche decided that scarcity was solved. Spin up what you need. Scale to meet demand. The infrastructure will handle it. This was always a category error. Elasticity is not abundance. It's the ability to acquire resources quickly, which is genuinely useful — but those resources still cost money, real money, money with line items and quarterly reviews attached to it. The "elastic" metaphor implies that the system returns to its original state, like a rubber band. Most autoscaling configurations do the opposite: they scale out aggressively and scale in lazily, because the engineers who configured them were optimizing for availability, not for cost. Of course they were. Availability failures page you. Cost failures invoice you three weeks later. This asymmetry in feedback latency is, I'd argue, the root cause of most cloud waste. You feel a reliability failure immediately, in your monitoring, in your error rates, in the angry emails from customers. You feel a cost failure at month-end, abstracted behind aggregates and allocation reports, at a distance from the specific code that caused it. The causal chain is so long and so obscured that attribution becomes genuinely difficult. Which service? Which deployment? Which query that suddenly started doing full table scans because someone dropped an index? You're doing forensic accounting on systems that didn't bother to leave evidence. Consider what actually happens inside a Lambda-based microservice when the retry logic goes wrong. A downstream dependency starts returning 429s — rate limiting, legitimate, expected under load. The Lambda function catches the error, implements exponential backoff, retries. Fine. Normal. But the backoff parameters were configured for a dependency that's usually briefly unavailable, not one that's rate-limiting at scale, and the jitter is insufficient, so you get retry storms: dozens of function instances all backing off to similar intervals, all hammering the dependency in synchronized bursts, all being rejected, all retrying again. Each invocation is cheap individually — fractions of a cent, execution measured in milliseconds. But you're running thousands of them simultaneously, each one burning GB-seconds of memory while waiting on a backoff interval, and the function is stateless so there's no circuit breaker state shared between invocations, and AWS will happily keep invoking your function at full concurrency because from its perspective, demand is high and capacity is available. Nobody gets paged. The error rate might actually look acceptable — most requests eventually succeed. Latency is elevated but within the p99 SLO. Meanwhile the bill for this three-hour incident is climbing toward what would normally be a week's worth of Lambda spend. This is the failure mode that the "cost as SLI" framing is trying to address, and it's worth being precise about what that means mechanically. A Service Level Indicator is a measurement of some property of the service's behavior. Latency, error rate, throughput — these are the canonical SLIs because they directly reflect the user experience. Cost doesn't appear in that list because it doesn't affect the user, not directly. But cost does reflect system behavior in ways that the other SLIs might not. A function that's executing correctly but expensively is exhibiting a real defect. The defect is just measured in dollars instead of milliseconds. Define it concretely: cost-per-request, tracked as a rolling average with a time window short enough to catch anomalies before they compound. For a Lambda function handling API traffic, this is derivable — you know the invocation count, you know the GB-seconds consumed, you know the memory configuration, you know the egress bytes. The math isn't complicated. What's missing in most stacks is the pipeline to compute it continuously and route it somewhere actionable. YAML - alert: CostPerRequestAnomaly expr: | ( increase(cloud_spend_dollars_total{service="payment-processor"}[30m]) / increase(http_requests_total{service="payment-processor"}[30m]) ) > 0.02 for: 15m labels: severity: warning annotations: summary: "Payment processor cost/request exceeding $0.02 threshold" runbook: "https://wiki.internal/runbooks/cost-anomaly" The alert above is simple to the point of being naive — a real implementation needs to handle the edge cases around division-by-zero when request volume drops, needs to account for fixed-cost components that don't scale with traffic, needs to be tuned per-service rather than applying a uniform threshold. But the principle is sound: instrument cost the way you instrument latency. Put it in the same pipeline. Give it the same alerting treatment. Let it page someone. Zombie resources deserve particular attention because they're so easy to dismiss as solved problems that keep not being solved. The inventory of forgotten things in a mature cloud environment is, in my experience, always larger than anyone expects. Unattached EBS volumes, left behind when instances were terminated but the delete-on-termination flag wasn't set. Elastic IPs not associated with any running instance, costing $0.005/hour each — individually trivial, collectively real money at scale. NAT Gateways in regions where you decommissioned the VPC workloads but left the gateway standing because the Terraform state wasn't cleaned up and touching Terraform state makes everyone nervous. RDS snapshots accumulating indefinitely because the backup retention policy was set aggressively and nobody wrote the cleanup job. Elastic Load Balancers with no healthy targets, passing health checks against nothing, billing for capacity they're delivering to nobody. The cumulative drag of this kind of waste is hard to calculate but easy to feel when you run the audit. It's almost never catastrophic individually. It's ambient cost noise that compounds month over month, gradually shifting the baseline upward so that each budget cycle starts from a floor that's slightly higher than the last one, and nobody can quite pinpoint why the efficiency curve keeps drifting in the wrong direction. Tooling exists for this — AWS Trusted Advisor, Compute Optimizer, the idle resource detection in Cost Explorer — but tooling that generates recommendations is only useful if someone's job is to act on them. That organizational detail is where most cost hygiene programs quietly fail. The recommendations accumulate in a dashboard somewhere. Engineers see them, acknowledge them, add them to a backlog, and then prioritize the feature work that someone with authority is actually asking for. The idle resources survive because their survival costs nobody anything immediately measurable. The fix isn't more tooling. It's accountability, specifically the kind that creates immediate feedback. Tag enforcement at resource creation — if you can't create a resource without a team tag and an environment tag and a project tag, the tagging happens. Automated cleanup of untagged resources after a grace period — not a suggestion, an actual termination, which focuses attention remarkably. Chargeback rather than showback: show teams what they're actually being charged for their cloud consumption, real money against real budgets, not just informational usage reports that feel abstract because no actual transfer occurs. Showback is useful. Chargeback is clarifying. Autoscaling deserves its own reckoning, because the failure mode isn't as simple as "it scales too much." Horizontal Pod Autoscaler configurations are typically written by engineers whose primary experience with the service was getting it to scale up fast enough during an incident. The scaling-out parameters get tuned aggressively; the scaling-in parameters stay at default or get made more conservative, because prematurely scaling in caused latency problems once and nobody wants that phone call again. The result is a ratchet: the cluster grows to accommodate load peaks and then stays grown, because the hysteresis is asymmetric. At a per-node cost of, say, $0.20/hour for a reasonable compute instance, running thirty nodes when twelve would suffice represents nearly $25,000 in annual waste — for a single workload. Multiply across services in a mid-sized platform organization and you're looking at numbers that fund engineering headcount. The reactive versus predictive scaling trade-off in the original article's table is real, but it understates the implementation cost of predictive scaling. Getting good predictions requires either historical data with stable periodicity (traffic patterns that repeat weekly, that kind of thing) or ML-based forecasting infrastructure with its own operational overhead. Most teams don't have clean enough signals to train reliable forecasting models, especially if their traffic has high variance or is driven by irregular external events — marketing campaigns, news cycles, competitor outages generating unexpected traffic. Scheduled scaling is more tractable: if you know from two years of logs that traffic increases 40% on weekday mornings at 9 AM in your primary timezone, you can pre-scale before that ramp rather than chasing it reactively. This doesn't require ML. It requires looking at your traffic patterns, which is a thing engineers often don't do because it doesn't feel like engineering. The honest trade-off isn't between reactive and predictive scaling as equivalent strategies with symmetric costs and benefits. It's between the certainty of reactive (it's always correct, just sometimes late) and the efficiency of predictive (it's cheaper when right, occasionally wrong, and wrong in ways that are visible and embarrassing). Most platform teams are better served by reactive scaling with more aggressive scale-in parameters and shorter cooldown windows than they currently run, plus scheduled pre-scaling for known patterns, than by investing in a forecasting infrastructure they won't maintain properly. What does a careful builder actually do on Monday morning? Probably not a complete FinOps transformation. Those take quarters, involve organizational dynamics that engineering can't resolve unilaterally, and have a tendency to generate dashboards that everyone nods at in the monthly review and nobody uses between reviews. Start with the instrumentation gap. Identify the three services that collectively drive the most spend — AWS Cost Explorer will tell you this, broken down by service type, in about five minutes. For each of those three services, answer the question: do you know what a normal cost-per-request looks like? If the answer is no, you don't have a cost monitoring problem, you have a cost observability problem, and that's the thing to fix first. Add the metrics, derive the baseline, set an alert threshold at 2x baseline as a starting point. You'll tune it. But you won't tune something you haven't measured. Then: run the idle resource report. Not for the purposes of immediately cleaning things up — though do that too — but to understand what the organizational failure mode is that produced those resources. Someone created them. Someone forgot them. Was there no offboarding process for decommissioned projects? Was there no budget owner for that cost center? Was the tagging policy unenforced? The idle resources are symptoms. The absence of process is the condition. And then — this is the uncomfortable one — have the conversation with whoever owns budget decisions about treating a cost anomaly the same way you'd treat a reliability incident: with a postmortem, with a timeline, with root cause analysis, with action items. Not blame. Not forensic punishment. The same blameless retrospective process you'd apply to a production outage, applied to a billing spike. Because a billing spike is a production incident. It just bills you for it differently. The systems are already distributed. The costs are already real. The instrumentation is the part you chose not to build yet. Build it.

By David Iyanu Jonathan
AI vs. Ageism: The Tech Industry’s Great Reset
AI vs. Ageism: The Tech Industry’s Great Reset

In the cutthroat world of technology, ageism has long cast a shadow over seasoned professionals. Layoffs targeting workers over 50 — epitomized by recent waves at Meta, Google, and Amazon — reveal a bias favoring youthful energy over accumulated wisdom. Yet, as AI tools explode in capability, a paradigm shift emerges: artificial intelligence isn't just automating jobs; it's supercharging the efficiency of older workers, blending their decades of insight with machine precision. This fusion could herald the death of ageism, positioning "long-living" professionals as indispensable assets for innovative companies. The Ageism Crisis in Tech: A Stark Reality Tech's youth obsession is no secret. A 2023 AARP report found that 1 in 5 workers over 50 face age discrimination, with tech hit hardest — median employee age at major firms hovers around 30-32, per Levels.fyi data. High-profile cases abound: Intel's 2024 layoffs disproportionately axed veterans, while startups shun "overqualified" applicants fearing cultural misfits. The rationale? Assumptions that older workers lag in adapting to rapid tech shifts, from cloud-native architectures to GenAI workflows. But this overlooks a goldmine: experience. Older professionals bring battle-tested judgment — spotting ethical pitfalls in AI deployments, architecting scalable systems from the mainframe era, or navigating stakeholder politics that sink 70% of digital transformations (per Gartner). The challenge has been proving their velocity matches the 20-somethings grinding 80-hour weeks. Enter AI. The Great Equalizer for Efficiency and Insight Generative AI democratizes productivity, erasing speed gaps that fuel age bias. Tools like GitHub Copilot, Claude, and Cursor now handle 40-55% of coding tasks, per GitHub's 2025 State of the Octoverse report — freeing humans for high-value work. For older developers, this means recapturing peak efficiency without the burnout of constant upskilling. Consider prompt engineering, AI's secret sauce. Seasoned pros excel here, leveraging contextual wisdom to craft precise instructions. A 2024 McKinsey study showed prompt-savvy users boost AI output quality by 30-50%; veterans' edge shines in nuanced scenarios, like generating secure microservices code or debugging legacy integrations. Example: A 58-year-old architect at a Fortune 500 firm used GPT-4o to prototype a Kubernetes-orchestrated app in hours, drawing on 30 years of deployment failures to refine prompts iteratively — output rivaled a junior team's weeks-long sprint. Beyond code, AI amplifies broader strengths: Knowledge Synthesis: Tools like Perplexity or Gemini summarize vast docs instantly, letting experts apply domain intuition without rote recall.Lifelong Learning Acceleration: Adaptive platforms (e.g., Duolingo for code via Replit AI) tailor training to experience levels, compressing years of ramp-up.Collaboration Boost: AI notetakers (Otter.ai, Fathom) and real-time copilots bridge generational gaps, turning mentorship into scalable superpowers. Real-world proof? IBM's 2025 pilot paired 50+ engineers with Watsonx; productivity surged 35%, with error rates dropping due to "insight-infused" prompts. Startups like Replicate report hiring 40+ talent post-AI, citing 2x faster innovation cycles. Why Companies Should Prioritize Older Pros: The Business Case Hiring gray hair isn't charity — it's strategy. Deloitte's 2025 Human Capital Trends flags "experience dividends" as key to AI-era resilience: older workers reduce project risks by 25% via foresight, per Harvard Business Review analysis. They mentor juniors effectively, curbing 40% turnover in Gen Z-heavy teams (Gallup data). Quantifiable wins include: AdvantageYounger WorkersOlder + AI WorkersBusiness ImpactProductivityHigh raw speedAI-amplified consistency20-40% faster delivery (McKinsey)InnovationBold ideasRefined, feasible execution30% higher success rates (Gartner)Risk MitigationTrial-and-error learningPreemptive issue spotting50% fewer production bugsRetentionHigh churn (25% annual)Loyalty (10-15% churn)$50K+ savings per roleDiversity ROIHomogeneous viewsCross-era perspectives19% higher revenue (BCG) Forward-thinking firms agree. Salesforce's 2026 hiring push targets 45+, armed with Einstein AI for seamless onboarding. "Experience compounds with AI," says CEO Marc Benioff. Governments echo this: EU's Digital Decade mandates age-diverse tech pipelines, backed by AI subsidies. Critics warn of resistance — older workers must embrace tools. Yet adoption rates rival youth: Stack Overflow's 2025 survey shows 62% of 50+ devs using AI daily, up from 12% in 2023. Embracing Meritocracy: Fair Chances for All Ages This vision is no zero-sum race pitting young against old. AI fosters true meritocracy, where talent triumphs regardless of age — evaluating contributions on impact, not calendars. Workplaces can and should host larger youth contingents for fresh dynamism, balanced by veterans' stabilizing force, creating multigenerational teams that outperform homogeneous ones by 20% in creativity (McKinsey). The goal: equitable opportunity, upskilling programs for all, and hiring that rewards proven value, ensuring tech's talent pool expands sustainably. A Reinvented Future: Long Live the Long-Living! AI doesn't replace wisdom; it resurrects it. By turbocharging efficiency and channeling time-won insights into prompts and strategy, it dismantles ageism's core myth: that tech demands perpetual youth. Companies ignoring this risk talent droughts amid 85 million AI-displaced jobs by 2030 (World Economic Forum). The call is clear: Tout older professionals as premium hires. Build AI-native roles celebrating their edge — Senior Prompt Architects, Insight Orchestrators. Tech's future belongs to the ageless: those who pair machine horsepower with human depth. As one 62-year-old CTO shared post-layoff reinstatement, "AI gave me my 30s back — and then some." Long live the long-living.

By Chimela Caesar
6 Books That Changed How I Think About Software Engineering in 2026
6 Books That Changed How I Think About Software Engineering in 2026

Reading is essential for everyone, and especially for software engineers. Our field centers on managing and advancing knowledge. As technologies and architectural paradigms evolve and challenges grow more complex, continuous learning becomes fundamental. In 2025, I read 34 books spanning philosophy, history, economics, and software engineering. While these subjects may seem unrelated to coding, they all aim to deepen our understanding of systems, whether in societies, economies, or software architectures. This article highlights six books that stood out for software engineers. Each offers lessons beyond technical implementation, covering strategy, leadership, learning, and design — skills that grow in importance as engineers progress in their careers. Some of these books are rereads. Revisiting valuable books often reveals new insights as our perspectives evolve. What once seemed theoretical may become highly practical when we encounter similar situations in real projects. Let’s start with a book that addresses one of the most misunderstood topics in engineering organizations: strategy. Crafting Engineering Strategy One of the most impactful books I read in 2025 was Crafting Engineering Strategy: How Thoughtful Decisions Solve Complex Problems by Will Larson. Many engineers assume their organization lacks an engineering strategy. In reality, most organizations already have one — it just might not be effective, explicit, or aligned with the company’s goals. Will Larson, also known for An Elegant Puzzle and as a staff engineer, provides a practical guide to navigating technical and organizational complexity through structured strategy. The book is especially valuable for senior engineers, architects, and engineering leaders who influence decisions beyond code. The author presents a repeatable process for building actionable engineering strategies, from diagnosing problems to communicating and implementing initiatives. Real-world examples from companies like Stripe, Uber, and Calm show how strategy shapes decisions on platform migrations, API deprecations, and infrastructure investments. Some of the most valuable lessons include: Building durable engineering strategies from first principlesApplying techniques such as Wardley Mapping and systems modelingLeading strategic initiatives as a staff+ engineer or engineering executiveLearning from real case studies across different industriesImproving long-term influence through structured thinking Engineering strategy is often seen as abstract or reserved for executives. This book clarifies that strategy is the structured alignment of technical decisions with long-term goals. While strategy and technical insight are essential, they are not the only factors in a successful engineering career. Often, the real differentiator is less technical. Emotional Intelligence Emotional Intelligence by Daniel Goleman offers an important perspective for software engineers: technical skills alone are not enough. In many organizations, engineers with strong technical capabilities are surprised when others — sometimes with less technical expertise — reach leadership positions faster. It is tempting to assume that the system is unfair. In reality, another factor is often at play: emotional intelligence. Daniel Goleman’s groundbreaking work explores how human behavior is shaped by two complementary systems: the rational mind and the emotional mind. While traditional intelligence (IQ) measures analytical ability, emotional intelligence (EI) includes qualities such as: Self-awarenessSelf-regulationEmpathySocial skillsMotivation These capabilities strongly influence collaboration, conflict resolution, communication, and leadership. Drawing on psychological and neurological research, Goleman explains why some with high IQs struggle professionally while others with moderate IQs succeed. Emotional intelligence shapes our ability to build trust, influence others, and navigate complex social environments — skills that grow in importance as engineers move into architectural or leadership roles. Another powerful insight from the book is that emotional intelligence is not fixed at birth. While childhood experiences shape it, EI can be developed throughout adulthood through reflection, feedback, and intentional practice. Recognizing this aspect of growth changes how we view engineering careers. The most successful engineers are not only technically strong but also understand people, teams, and organizational dynamics. This naturally brings us to the next topic: how engineering teams actually function and succeed in practice. Leading Effective Engineering Teams Leading Effective Engineering Teams by Addy Osmani is another standout book from my 2025 reading list. Drawing on over a decade with the Chrome team at Google, Osmani examines what makes engineering teams effective. The book addresses both individual contributors and engineering managers. One of the key themes of the book is the distinction between efficiency, effectiveness, and productivity — three concepts that are often used interchangeably but actually represent very different things. Efficiency focuses on doing tasks quickly.Productivity measures output.Effectiveness measures whether the work actually delivers meaningful impact. In engineering teams, optimizing the wrong metric can cause problems. Teams focused solely on productivity may generate large volumes of code without delivering real value. Osmani emphasizes that effective teams are built on trust, accountability, and clear communication. The book offers practical guidance on topics such as hiring, mentoring, career growth, and building sustainable engineering culture. Some highlights include: Traits of highly effective engineers and teamsTechniques for fostering trust and accountabilityStrategies to minimize friction in collaborationSystems thinking approaches for daily engineering decisions.Methods for improving visibility and recognition within organizations The most valuable lesson is that engineering excellence is rarely achieved alone. It almost always results from a healthy team culture. Once we understand how teams function, the next natural question becomes: how should we design the systems those teams build? This leads us to a topic that is often misunderstood in software architecture. Balancing Coupling in Software Design When software engineers first study architecture, one concept appears repeatedly: coupling. The message is almost always the same: coupling is bad. However, Balancing Coupling in Software Design by Vlad Khononov challenges this simplistic perspective. Coupling is not inherently bad. In fact, it is unavoidable. Every design decision we make introduces some form of coupling. The real challenge is understanding and controlling it. Khononov explores how coupling affects modularity, system evolution, and long-term maintainability. The book builds upon decades of research in software engineering while adapting those concepts to modern architectural practices such as microservices, domain-driven design, and distributed systems. Rather than treating coupling as something to eliminate, the book presents it as a design dimension that must be balanced. Some key insights include: Understanding different types of coupling in software systemsUsing coupling intentionally to manage complexityRecognizing trade-offs between modularity and system cohesionApplying design principles that support long-term evolution This perspective is especially valuable for architects and senior engineers who must balance flexibility, performance, and maintainability. Even the best design principles are ineffective if engineers cannot continuously learn and adapt. Given the rapid pace of change in our industry, learning is a core engineering skill. Ultralearning Ultralearning: The Essential Guide to Mastering Hard Skills and Future-Proofing Your Career by Scott H. Young focuses on one of the most critical abilities for modern professionals: learning efficiently. Software engineers constantly encounter new frameworks, languages, architectures, and methodologies. The challenge is not only learning new technologies but also deciding what is worth learning. Young introduces the concept of ultralearning, an intense and structured approach to mastering complex skills quickly. The book presents nine principles that help individuals learn deeply and effectively through self-directed education. Some of the ideas explored include: Direct learning through real projectsStrategic practice and feedback loopsRetrieval-based learning instead of passive readingExperimentation and adaptation of learning strategies The book highlights historical and modern ultralearners, such as Benjamin Franklin, Richard Feynman, and Judit Polgár, showing that structured self-learning has long driven mastery. For software engineers, this mindset is particularly valuable. The industry evolves rapidly, and those who learn efficiently gain a significant advantage over time. However, learning and design are only part of the equation. Without effective knowledge sharing, teams and organizations struggle to stay aligned. Docs Like Code Documentation remains one of the most underestimated aspects of software engineering. In many organizations, teams fall into one of two extremes. Either documentation is almost nonexistent, forcing engineers to rely on meetings and tribal knowledge, or there is an overwhelming amount of documentation that becomes outdated and ignored. Docs Like Code: Collaborate and Automate to Improve Technical Documentation introduces a more balanced approach. The core idea is simple: Treat documentation the same way we treat code. This means applying practices such as: Version controlCode reviewsContinuous integrationAutomated validationCollaborative workflows By integrating documentation into the development lifecycle, teams can ensure that knowledge evolves alongside the codebase. The result is documentation that remains relevant, maintainable, and useful, rather than becoming an abandoned artifact. For engineers focused on system design and long-term maintainability, this approach transforms documentation from a bureaucratic task into an essential engineering practice. Final Thoughts Reading remains one of the most powerful habits a software engineer can develop. The books highlighted here address various aspects of engineering growth: strategy, emotional intelligence, team dynamics, architectural design, learning, and documentation. Together, they offer a broader perspective on growing beyond coding to become a more complete engineer. Software engineering is not only about building systems. It also involves understanding complex environments, collaborating with others, making strategic decisions, and continuously learning. Sometimes, the best way to improve as an engineer is simply to start with a good book.

By Otavio Santana DZone Core CORE
Runtime FinOps: Making Cloud Cost Observable
Runtime FinOps: Making Cloud Cost Observable

There's a particular kind of learned helplessness that settles into engineering organizations after a few years of rapid cloud growth. You ship a feature. The feature works. Latency looks fine, error rates stay quiet, on-call doesn't page. Then three weeks later someone from finance drops a Slack message — a screenshot of the AWS Cost Explorer with a jagged upward spike, annotated with a red arrow and a question mark. By then, the deployment that caused it has been buried under six more deploys. The engineer who wrote the change is mentally two features ahead. Nobody remembers. You run a postmortem on nothing. This is the default state for most shops. Not negligence, exactly. More like a structural information deficit: the feedback loop between code change and cost impact is measured in billing cycles, not seconds. Runtime FinOps is the attempt to collapse that latency. The core mechanical insight is embarrassingly simple once you see it. Cloud spend is ultimately a function of resource consumption, which is itself a function of workload behavior, which is directly caused by deployed code. The causal chain is unbroken. What's broken is the observability of that chain — the instrumentation stops at runtime metrics and never continues downstream into the dollar layer. Prometheus scrapes CPU and memory. Datadog tracks p99 latency. Nobody is emitting cost_per_request_dollars into the same time-series store. That gap isn't accidental. It reflects organizational archaeology — engineering tools were built by engineers who didn't own the bill, and finance tools were built by accountants who didn't understand deployment pipelines. The FinOps movement as a discipline has largely tried to paper over this by creating shared dashboards and monthly reviews. That's better than nothing. It is not remotely sufficient. What sufficient looks like: a Grafana panel, sitting next to your latency and throughput charts, showing dollars-per-minute in something close to real time. Not aggregated monthly, not delayed by the 24-to-48-hour lag that AWS billing data typically carries, but live. Or close to live. And critically, annotated — vertical lines at every deploy, tagged by Git SHA, so when the cost curve flexes upward you can see which change correlated with when. Tools like Kubecost and CloudZero attempt this for containerized workloads, mapping cluster resource consumption to workloads and namespaces with reasonable accuracy. The attribution model involves some approximation — particularly around shared infrastructure, node-level overhead, and storage that doesn't decompose cleanly to individual pods — and practitioners would be dishonest if they called it precise. It's directionally accurate. In FinOps, directionally accurate and fast beats precisely accurate and three weeks late every single time. The tagging problem deserves its own meditation, because this is where ambition usually fractures against operational reality. The idea is clean: every cloud resource carries tags — service, team, environment, git-sha, pr-number — and those tags flow through billing, letting you attribute cost to the unit of work that caused it. In theory, you can then answer "what did this pull request cost us in production over its first 72 hours of traffic?" In practice, tagging compliance in most organizations sits somewhere between 40% and 70% on a good day, because tags are set at resource creation and then drift, or get set inconsistently across Terraform modules, or simply aren't applied to resources provisioned through the console in a hurry. Data transfer costs — often a substantial portion of a distributed system's bill — aren't taggable in any meaningful way. RDS instance costs don't decompose to the query or calling service. The tag taxonomy you design in January will be partially obsolete by June when someone creates a new microservice and doesn't know the convention. None of this means tagging is futile. It means the feedback loop you build on top of tags is only as trustworthy as your tagging governance, and tagging governance requires someone to actually own it, which requires organizational will that frequently isn't there. The more robust pattern I've seen in practice: tag at the workload level (not the resource level), enforce it via CI/CD gate rather than relying on humans to remember, and accept that you'll have a residual "unattributed" bucket that you manage down over time rather than eliminating entirely. Tools like AWS Tag Editor and custom OPA policies for Terraform can close the loop on net-new resources. The legacy tail requires a different, less glamorous approach: manually audit, assign, iterate. The CI/CD integration story is where things get genuinely exciting, and also where practitioners should calibrate their expectations carefully. Infracost is the canonical example: it parses Terraform plan output, estimates the monthly cost delta of the proposed infrastructure change, and posts that estimate as a comment on the pull request. This is legitimately useful. A PR that adds three RDS read replicas and a NAT gateway should trigger a cost conversation before it merges, not after the bill lands. Engineers who see "this change will add ~$340/month" in their PR review interface learn, over time, a working intuition about what infrastructure costs. That intuition is rarer than it should be. The limitation is that Infracost and its peers estimate infrastructure cost — the static resource footprint — rather than operational cost, which includes data transfer, API calls, Lambda invocations, storage I/O, and everything else that scales with traffic and behavior rather than existence. A change that looks cost-neutral at the infrastructure level might double your CloudFront egress if it changes response payload sizes. It might triple your DynamoDB read units if it introduces a hot key. The tools don't know this. They can't, without runtime data. The more sophisticated version of this loop, which fewer teams have built, uses predictive cost modeling against actual traffic. You have a deployment. You have the last N days of traffic patterns. You can project forward: "given current traffic, this new resource configuration will consume approximately $X over the next 30 days." AWS Cost Explorer has a forecast API. Combining it with deployment annotation is not a huge engineering lift, but it requires someone to actually build and maintain the plumbing. Most teams haven't made that investment. Consider what an SRE-inflected cost culture actually demands. SRE borrow two concepts that apply almost without modification: error budgets and anomaly alerting. An error budget for cost would look like this: the service owns a monthly cost envelope, approved and visible, and the team tracks burn rate against it the way they track error budget burn against their SLO. When burn rate exceeds a threshold — say, the monthly budget will be exhausted in 20 days at current trajectory — that's an alert, the same severity as a latency SLO violation. Not a finance report. A PagerDuty ticket if you want to be maximalist about it, or at minimum a Slack alert that reaches the on-call engineer, not the VP of Engineering. AWS Cost Anomaly Detection does a serviceable version of this out of the box, using ML to detect spend patterns that deviate from the expected baseline and sending SNS notifications. It's underused. I suspect this is partly because the notification goes to whoever set up the billing alert (often a platform team, sometimes a finance person) rather than to the team that owns the service. The alert finds the wrong inbox and dies there. The organizational fix is unglamorous: route cost anomaly notifications to the same escalation paths as operational incidents. The same service catalog that maps an alert to an on-call rotation should map a cost anomaly to the team that owns the relevant tagged resource. This requires the tagging to work. Everything requires the tagging to work. There's an architectural pattern worth naming explicitly: cost as a flow control signal. In a well-instrumented system, you might have a service that responds to demand by scaling out — adding pods, provisioning more compute, whatever the autoscaling policy dictates. This is good. Autoscaling is good. But autoscaling policies are typically expressed in terms of CPU utilization or queue depth or request rate, never in terms of "we have now spent $X in the last hour and this is abnormal." A traffic spike from a misbehaving client, a scraper, an accidental infinite loop in a partner's integration — these can drive spend through the ceiling before any CPU-based autoscaler would even notice a problem. Dollar-rate alerting fills a different detection envelope than performance alerting. A pathological client that sends low-volume but expensive requests — each one triggering a chain of downstream API calls, S3 reads, expensive ML inference — might not move your CPU metrics at all. It will move your bill. If you're watching dollars-per-minute in Prometheus and the rate doubles, that signal is available to you immediately. Whether you act on it programmatically (rate limiting, circuit breaking, graceful degradation) or operationally (alert, investigate, remediate) is a choice, but you can't make it if you can't see it. The blameless postmortem for cost incidents is a concept that sounds slightly ridiculous the first time you hear it and becomes obviously correct about sixty seconds later. When a cost spike happens, the natural instinct in most organizations is either to ignore it (it's just money, nobody died) or to hunt for the responsible party and make an example of them. Both responses are bad. Ignoring it means the behavior repeats. Making an example of someone means engineers become risk-averse about infrastructure changes in ways that slow down the whole organization. The SRE approach to operational incidents — reconstruct the timeline, identify contributing factors, generate mitigations, share the learning broadly — transfers completely. What was the change that caused the spike? Was it a code change, a configuration change, an unexpected shift in traffic? Was it even caused by a change, or is it an emergent behavior of a system that was always going to fail this way under sufficient load? What could have caught it earlier? What will catch it next time? The output of that process is institutional knowledge and, eventually, changed defaults. The team that burns their cost budget on an accidentally O(n²) database query and runs a postmortem on it will write better queries afterward, not out of fear but because they now have a concrete understanding of what "better" means in dollar terms. Honestly, the biggest obstacle isn't technical. The tools exist. Kubecost, CloudZero, Infracost, CloudHealth, AWS-native cost tooling — the ecosystem is mature enough that you can build a meaningful runtime FinOps practice without writing much novel infrastructure. The pipeline from resource consumption to tagged cost attribution to developer-facing dashboard is navigable. What isn't navigable without organizational agreement is the question of who owns this. Finance owns the bill but not the code. Engineering owns the code but not the budget. Platform teams own the tooling but not the individual services. FinOps functions, where they exist, often sit in a liminal space that has advisory authority but not operational authority. None of these entities, alone, can close the feedback loop. The teams that actually do this well tend to have one thing in common: a clear owner at the service level. Not "the platform team will build cost dashboards for everyone" but "this service team owns a cost SLO, reviews it in their weekly ops meeting, and is the first call when a cost anomaly fires." That's a cultural stance, not a technical one. If you wanted to change something by Monday morning, the smallest high-signal move is this: find your last three significant cost spikes, look at the deployment timeline, and see whether you can identify the correlating change. Do this manually, in AWS Cost Explorer, cross-referenced against your deployment log. If you can correlate them — if the mechanism is visible in retrospect — you now have a concrete example to show your team of what a runtime cost signal would have caught in real time. That example is worth more than any amount of abstract advocacy for FinOps practices. Then ask yourself: what's the minimum instrumentation that would have surfaced this signal at deploy time? Maybe it's a CloudWatch alarm on spend rate. Maybe it's a Kubecost dashboard with a deployment annotation. Maybe it's just a Slack alert from Cost Anomaly Detection routed to the right channel. Start there. The elaborate CI/CD cost gates and per-Git-SHA bill-of-materials and predictive spend forecasting are all real and all worthwhile, but they're downstream of a simpler belief: that cloud spend is a system metric, not a finance report, and your observability stack should treat it that way. The rest follows.

By David Iyanu Jonathan
Accelerating Your Software Engineering Career With Open Source and Jakarta EE
Accelerating Your Software Engineering Career With Open Source and Jakarta EE

For decades, software engineering followed a relatively predictable path: learn the language, master the tools, deliver results, and progress. That model is quietly breaking. Today, engineers are expected to do more than build systems — they are expected to influence decisions, communicate across teams, and demonstrate impact beyond their immediate environment. Yet most career advice still focuses solely on improving technical skills. This creates a gap. In this article, we explore how open source — especially through Jakarta EE — fills that gap, turning everyday engineering work into something visible, scalable, and career-defining. The Challenge of Modern Software Careers Once we accept that technical excellence alone is no longer enough, the next question becomes unavoidable: What actually sustains a software engineering career today? The industry has changed in subtle but significant ways. Stability has decreased, expectations have expanded, and the definition of value has shifted. Engineers are no longer evaluated only by their ability to deliver features, but by their capacity to influence decisions, communicate ideas, and operate beyond the boundaries of their immediate team. This creates a tension. Many engineers continue to invest heavily in technical preparation — learning frameworks, improving coding practices, studying architecture — yet still feel stuck. The issue is not always a lack of effort, but often a mismatch between effort and opportunity. Preparation, in isolation, does not scale if it remains invisible. Historically, engineering was never just about tools. The term itself comes from ingenium, referring to ingenuity, creative problem-solving, and the capacity to devise solutions under constraint. That older meaning matters because it reminds us that engineering is not simply technical execution; it is the disciplined application of judgment. But judgment alone does not guarantee opportunity. This is where Seneca becomes surprisingly modern. He is often paraphrased as saying that luck is what happens when preparation meets opportunity. Whether we call it luck, chance, or timing, the principle is the same: opportunity favors those who are already in motion. In the context of a software career, this means waiting to become visible only when the perfect opportunity arises is already too late. We need preparation, certainly, but also visibility and adaptability, because in practice these are what allow preparation to encounter opportunity at all. That is why the real challenge of the modern career is not only becoming good, but becoming discoverable, credible, and ready. And this is exactly where open source and open standards begin to matter. Open Source and Open Standards as Career Leverage Open source is often misunderstood. It is frequently treated as a side activity, something optional or even altruistic. But if we examine it more carefully, open source functions as a mechanism for making work visible at scale. It transforms private effort into public evidence. Instead of describing your experience, you expose it. Instead of claiming expertise, you demonstrate it. This distinction matters because traditional career signals — résumés, certifications, interviews — attempt to infer capability. Open source reduces that distance. It allows others to see how you think, how you collaborate, how you respond to criticism, and how you improve an idea over time. In that sense, open source becomes more than a technical activity. It becomes a form of preparation made visible. And that returns us to Seneca’s insight: Preparation without contact with the world remains incomplete. It is only when knowledge becomes visible and testable in public that it can truly meet opportunity. But open source alone is only part of the picture. To understand why it can have such a strong effect on a career, we need to add another concept: open standards. Historically, standards have been among the great enablers of civilization. Shared language allowed cooperation beyond small groups. Writing preserved thought across generations. Standard units of measure made trade, engineering, and science reliable. Human progress did not scale merely because people were talented; it scaled because meaning became shareable. Software is no exception. As systems become larger and more interconnected, a lack of standards leads to fragmentation, lock-in, and unnecessary complexity. Open standards address this by defining shared expectations independently of a single implementation. They create stability without demanding uniformity from vendors. When open source and open standards work together, something unusual happens. Open source creates transparency, collaboration, and visibility. Open standards create consistency, interoperability, and durability. One opens the door to participation; the other ensures that what is built can endure beyond a single company or framework. For software engineers, this combination is particularly powerful. It means that contributing is not only about fixing code or adding features. It is also about entering into a wider conversation about how systems should be designed, how technologies should evolve, and how collaboration can scale across organizations. This is why open ecosystems are so valuable for a career. They do not merely improve technical skill; they train judgment, communication, and long-term thinking. And few examples in enterprise Java illustrate this intersection as clearly as Jakarta EE. Jakarta EE: Where Open Source Meets Enterprise Reality Jakarta EE represents a convergence of these ideas in the Java ecosystem. At its core, it provides vendor-neutral APIs intended for long-lived enterprise applications. On the surface, that may sound like a technical description. In reality, it reflects a broader philosophy: software should evolve without forcing organizations into permanent dependency on a single implementation. This matters because enterprise systems are rarely short-lived. They are designed to survive years of evolving requirements, teams, and infrastructure. Without standards, this continuity becomes fragile. With them, systems gain a degree of resilience and predictability. That is why Jakarta EE is more than a framework discussion. It is an example of how open standards and open source can coexist to serve real business needs. It provides a shared foundation while still allowing multiple implementations, vendors, and runtimes to participate. This reduces fragmentation and makes enterprise Java more coherent over time. For engineers, engaging with Jakarta EE introduces a deeper layer of professional growth. The questions shift from local implementation details to broader design concerns. How should an API behave across environments? How do we preserve compatibility while allowing evolution? How do we create something that remains useful beyond the immediate preferences of one team or one company? These are not only coding questions. They are architectural and even philosophical questions, because they concern continuity, cooperation, and trade-offs over time. And that brings us back, quietly, to the same principle. If modern careers require preparation, visibility, and adaptability, then Jakarta EE offers a space where all three can be exercised together. It is certainly technical work, but it is also public, collaborative, and durable work. In other words, it is preparation in a form that has a real chance of meeting an opportunity. Still, understanding the value of such an ecosystem is one thing. Applying it to daily life is another. Applying This in Practice: From Knowledge to Career Movement Knowing that open source and standards can accelerate a career does not, in itself, change anything. The practical question is how to make this part of one’s professional life without turning it into burnout or abstraction. The first answer is consistency. Many engineers approach open source in bursts of enthusiasm, contributing intensely for a few days or weekends and then disappearing. But careers, much like reputations, are built less by intensity than by continuity. Seneca, in his Stoic way, repeatedly emphasized discipline over impulse. That applies here as well. A small, consistent contribution is often more transformative than an occasional heroic effort. The second answer is to treat open source as training, not as performance. At the beginning, the work may feel invisible or unpaid, and that can be discouraging. But this is precisely where long-term thinking matters. You are not only contributing code; you are learning to write clearly, to discuss ideas, to review systems critically, and to operate in public. These are career assets that compound. The third answer is communication. No meaningful open ecosystem works without it. Engineers must learn to explain decisions, respond respectfully, document clearly, and engage across cultures. This is one reason English becomes so important in practice. In software, English functions almost like musical notation in music: it is the medium through which participation becomes possible at scale. Learning it early is not merely a linguistic advantage; it is access to the broader conversation. The fourth answer is balance. A career is not strengthened by sacrificing everything to it. One of the oldest philosophical lessons, not only in Stoicism but in ethics more broadly, is that discipline without measure becomes self-destruction. Open source should expand your life, not consume it. Saying no, focusing on what matters, and accepting that no one can master the entirety of IT are signs of maturity, not weakness. And finally, there is the matter of visibility. Being skilled is essential, but it is not enough if your work never leaves the confines of the private sector. Visibility is not vanity. Properly understood, it is the process by which trust becomes possible. When people can see what you build, how you reason, and how you contribute, they have something concrete on which to base their confidence. Over time, this changes the nature of career opportunities. Instead of constantly needing to prove yourself from zero, your work begins to speak ahead of you. Conclusion: Preparation Meeting Opportunity If there is a single thread connecting all of this, it is the old Stoic insight we started with: Opportunity does not belong to those who merely hope for it, but to those who prepare in a way that allows chance to find them. That is why the modern software career cannot be reduced to technical competence alone. Preparation still matters, but it must now be visible and adaptable. Open source gives that preparation a public form. Open standards give it structure and durability. Jakarta EE shows how both can come together in a practical, long-lived, and globally relevant enterprise setting. The result is more than better code. It is credibility, trust, and a career foundation that extends beyond a single employer or moment in the market. In uncertain times, that may be the closest thing to stability we can build for ourselves. And perhaps Seneca would recognize the pattern immediately: we do not control when opportunity appears, but we can control whether we are ready when it does.

By Otavio Santana DZone Core CORE
Serverless Glue Jobs at Scale: Where the Bottlenecks Really Are
Serverless Glue Jobs at Scale: Where the Bottlenecks Really Are

At moderate volumes, AWS Glue feels almost effortless. You increase workers. The job runs faster. You double the input size. Runtime roughly doubles. Everything behaves predictably. Then one day, it stops behaving that way. We had a job that ran in about 15 minutes. The dataset grew. Runtime climbed to 27. That made sense. We increased workers. It dropped to 22. We increased workers again. It dropped to 21. That was the moment it became clear we weren’t compute-bound anymore. What slowed the job down wasn’t CPU. It wasn’t memory. It wasn’t even S3 read time. It was shuffle. It was skew. And it was file behavior. This article walks through the experiments I ran to understand where Glue jobs really break at scale — and what actually fixes them. The Setup The pipeline is simple. Raw transaction-style data lands in S3. A Glue job transforms it, joins it to a small dimension table, aggregates it, and writes the result back to S3. Sometimes the output is plain Parquet. Sometimes it’s written to Iceberg. No streaming. No ML. No exotic orchestration. Just Spark running inside Glue. To keep this reproducible, I generated synthetic data inside Spark. Python from pyspark.sql import functions as F def generate_transactions(rows: int): return ( spark.range(rows) .withColumnRenamed("id", "txn_id_num") .withColumn("account_id", (F.col("txn_id_num") % 500000).cast("string") ) .withColumn("amount", (F.rand(42) * 500).cast("double")) .withColumn( "txn_ts", F.expr(""" timestampadd( MINUTE, cast(txn_id_num % 100000 as int), timestamp('2025-01-01 00:00:00') ) """) ) .withColumn("txn_date", F.to_date("txn_ts")) .withColumn("merchant_code", (F.col("txn_id_num") % 10000).cast("int") ) .drop("txn_id_num") ) This lets us scale from 5 million rows to hundreds of millions without introducing unknown variables. Where a Glue Job Can Actually Slow Down Before going deeper, it helps to anchor the discussion in how a Glue job actually executes. Most Glue job performance issues map cleanly to one of five phases: Read from S3TransformShuffleWriteCommit metadata (optional) When Scaling Still Works The first experiment was intentionally simple. Python df = generate_transactions(50_000_000) df_baseline = ( df.withColumn("amount_bucket", F.when(F.col("amount") < 50, "LOW") .when(F.col("amount") < 200, "MED") .otherwise("HIGH") ) .groupBy("txn_date") .agg(F.count("*").alias("txn_cnt")) ) At 5 million rows, the job was quick. At 50 million, runtime increased proportionally. At 200 million, it was slower but still predictable. This is what Spark does well: narrow transformations and simple aggregations scale cleanly. The problems start when the workload becomes wide. The Shuffle Shift Things changed as soon as I introduced a join and grouped by a higher-cardinality key. Python dim = ( spark.range(0, 10000) .withColumnRenamed("id", "merchant_code") .withColumn("merchant_category", F.concat(F.lit("cat_"), (F.col("merchant_code") % 50)) ) ) df_joined = df.join(dim, "merchant_code", "left") df_agg = ( df_joined.groupBy("txn_date", "merchant_category") .agg( F.count("*").alias("txn_cnt"), F.sum("amount").alias("total_amount") ) ) Runtime increased, but what mattered more was what the Spark UI showed. Most of the time was now spent inside shuffle stages. CPU wasn’t pegged. Executors weren’t maxed out. But the shuffle stage consumed the majority of runtime. That distinction matters. When a job is compute-bound, adding workers usually helps. When a job is shuffle-bound, the bottleneck shifts to data movement. Shuffle is not just another transformation. It is full data redistribution across the cluster. Rows are repartitioned by key, exchanged across executors, and often written to disk before being merged again. It is network-heavy. It is disk-heavy. And it is extremely sensitive to key distribution and imbalance. Once shuffle dominates runtime, adding workers produces diminishing returns. Why? Because you are no longer limited by raw compute. You are limited by how evenly data can be distributed across partitions. Skew: The Silent Runtime Killer To test skew, I ran: Python df.groupBy("account_id") \ .count() \ .orderBy(F.desc("count")) \ .show(10) A few keys had dramatically more rows than others. That explains long-running tasks at the tail of shuffle stages. Distributed systems are only as parallel as their most overloaded partition. One partition holding millions of rows can stall the entire stage. Salting as a Controlled Tradeoff One mitigation is salting: Python salted = df.withColumn("salt", (F.rand() * 10).cast("int")) salted = salted.withColumn( "account_salted", F.concat_ws("_", "account_id", "salt") ) This spreads large keys across partitions. It improves parallelism. It also increases shuffle complexity and requires careful downstream handling. Salting is a tradeoff, not a universal fix. The Partitioning Trap The most dramatic slowdown wasn’t from shuffle. It was from partitioning. Partitioning by txn_date behaved well. Partitioning by account_id looked logical. It wasn’t. Python df.write \ .mode("overwrite") \ .partitionBy("account_id") \ .parquet("s3://bucket/account_partition/") The result: File counts explodedWrite time increased significantlyAverage file size dropped sharply High-cardinality partitioning multiplies partitions and files. Each Spark task can write one file per partition. At scale, that becomes thousands of files. Small Files Are Not Harmless Small files affect: S3 object listingQuery planningMetadata operationsCompaction requirements The fix is not random repartitioning. It’s intentional shaping. Python df.repartition(200, "txn_date") \ .write.partitionBy("txn_date") \ .parquet("...") Aligning repartitioning with partition columns reduces file chaos. When Iceberg Enters the Picture Writing plain Parquet exposes file-level problems. Writing to Apache Iceberg adds a metadata layer. Each write creates: Data filesManifest entriesA snapshotA commit operation If file counts are high, commit time grows. If partitions are excessive, manifest lists expand. Creating the table is straightforward: SQL CREATE TABLE transactions_iceberg ( txn_id STRING, account_id STRING, txn_ts TIMESTAMP, amount DOUBLE ) USING iceberg PARTITIONED BY (days(txn_ts)); Writing is equally simple: Python df.repartition(200, "txn_date") \ .writeTo("catalog.db.transactions_iceberg") \ .append() The complexity shows up later: Slower planningGrowing snapshot historyMetadata overhead that scales with file count Iceberg doesn’t create performance issues. It amplifies poor file discipline. The Serverless Ceiling There is a point where: Shuffle dominates runtimeSkew stalls a subset of tasksFile creation dominates write timeCommit time becomes visibleIncreasing workers has minimal effect At that point, the scaling curve flattens. That’s the serverless ceiling. Adding more workers doesn’t help. Reshaping the workload does. Reducing shuffle width. Managing skew. Designing sane partition strategies. Controlling file size intentionally. Those changes moved runtime more than any worker increase did. Closing Thought Serverless removes cluster management. It does not remove distributed systems physics. Data movement still costs. Imbalance still hurts. Files still matter. Metadata still accumulates. Once you start thinking in terms of workload shape instead of raw compute, Glue scaling becomes much more predictable. And the next time a job jumps from 15 minutes to 40, you’ll know exactly where to look.

By Vivek Venkatesan

Monthly Top Career Development Experts

expert thumbnail

Miguel Garcia

VP of Engineering,
Factorial

Miguel has a great background in leading teams and building high-performance solutions for the retail sector. An advocate of platform design as a service and data as a product.
expert thumbnail

Gaurav Gaur

Staff Software Engineer

The Latest Career Development Topics

article thumbnail
Open Source as a Leadership Lab for Software Engineers
Beyond code, open source offers real opportunities to practice communication, influence, collaboration, discipline, and decision-making.
August 21, 2026
by Otavio Santana DZone Core CORE
· 312 Views
article thumbnail
You Don’t Need To Be a Manager To Lead: Why Leadership Matters for Software Engineers
As software engineers grow in scope, trust, influence, communication, and technical direction become essential to multiplying impact and advancing on the IC path.
August 20, 2026
by Otavio Santana DZone Core CORE
· 1,009 Views · 1 Like
article thumbnail
How to Design a Distributed Job Scheduler
One cron line breaks once you have more than one server. Learn to design a distributed job scheduler that runs each job once, survives crashes, and retries.
August 6, 2026
by Ajit Singh
· 1,994 Views · 3 Likes
article thumbnail
Build Your Own Local AI QA Engineer With Docker, Ollama, LibreChat, and Playwright MCP
Learn how to build a completely local AI-powered QA Automation Engineer using Docker, Ollama, Qwen3:8b, LibreChat, and Playwright MCP.
July 30, 2026
by Faisal Khatri DZone Core CORE
· 7,403 Views
article thumbnail
Top 10 Best Places to Prepare for Your Next Data Engineer Interview
Candidates must demonstrate strong SQL, Python, data modeling, ETL, Spark, data warehousing, and system design expertise while solving real-world data challenges.
July 10, 2026
by Rahul Han
· 2,157 Views · 1 Like
article thumbnail
Building an Idempotent Job Queue in Node. js That Never Runs the Same Task Twice
Message queues will inevitably redeliver jobs, leading to critical duplicate side effects like double-charging customers.
July 8, 2026
by Bilal Azam
· 2,277 Views
article thumbnail
The 20 Software Engineering Laws
20 software engineering laws that explain why rewrites fail, late projects slip, and teams game every metric. They're about people under pressure, so they still hold.
June 30, 2026
by Milan Milanovic DZone Core CORE
· 3,776 Views · 12 Likes
article thumbnail
The New Senior Developer Job Description: Half Engineer, Half AI Systems Architect
Senior developers now own two roles: traditional engineering plus AI systems architecture. This split reshapes compensation, hiring, and what 'senior' actually means.
June 30, 2026
by Dinesh Elumalai DZone Core CORE
· 3,527 Views · 4 Likes
article thumbnail
Amazon Quick: AWS's Agentic Workspace, Explained for Engineers
A technical deep dive into Amazon Quick — how it works, how it connects to your tools via MCP, and where it sits in the AWS agent stack.
June 9, 2026
by Jubin Soni, FBCS DZone Core CORE
· 4,383 Views
article thumbnail
Why Your QA Engineer Should Be the Most Stubborn Person on the Team
Strong QA is not checklist work. It combines investigation, analytical thinking, and technical communication to find failure paths early and improve the system over time.
May 14, 2026
by Alex Vakulov DZone Core CORE
· 2,128 Views · 3 Likes
article thumbnail
You Learned AI. So Why Are You Still Not Getting Hired?
Most AI job seekers learn tools. Employers hire people who can specify tasks, evaluate outputs, manage risk, and deliver real business value with AI.
May 13, 2026
by Faisal Feroz
· 5,530 Views · 4 Likes
article thumbnail
Stop Using the ATM-Didn’t-Kill-Jobs Story to Reassure Developers About AI
The ATM didn’t kill bank tellers’ jobs — the iPhone did. Getting the history right isn’t reassuring; it clarifies why developers should pay attention.
May 13, 2026
by Thomas Johnson DZone Core CORE
· 3,661 Views
article thumbnail
AI Didn't Replace Seniors; It Just Made Them the Bottleneck
The code generation era shipped a paradox: faster output, slower understanding, and a talent pipeline headed for collapse.
May 5, 2026
by Abgar Simonean
· 2,861 Views · 3 Likes
article thumbnail
Cost Is an SLI: Why Your System Is “Healthy” but Burning Cash
Runaway cloud spend hides in healthy systems — driven by poor cost visibility, idle resources, and scaling inefficiencies. Fix it with cost-per-request metrics.
May 4, 2026
by David Iyanu Jonathan
· 1,906 Views
article thumbnail
AI vs. Ageism: The Tech Industry’s Great Reset
AI is erasing tech’s age bias by boosting older workers’ speed and amplifying their experience—making them more productive, reliable, and valuable than ever.
April 28, 2026
by Chimela Caesar
· 2,182 Views · 2 Likes
article thumbnail
Runtime FinOps: Making Cloud Cost Observable
Treat cloud cost as a real-time system metric tied to deployments. With tagging, CI/CD estimates, and alerts to service owners, teams can catch spend spikes early.
April 15, 2026
by David Iyanu Jonathan
· 2,993 Views
article thumbnail
6 Books That Changed How I Think About Software Engineering in 2026
These six reshaped how I think about engineering: strategy, emotional intelligence, team effectiveness, software design coupling, ultralearning, and docs-as-code.
April 9, 2026
by Otavio Santana DZone Core CORE
· 5,109 Views · 2 Likes
article thumbnail
Accelerating Your Software Engineering Career With Open Source and Jakarta EE
Open source turns preparation into visibility. Combined with open standards like Jakarta EE, it builds credibility, adaptability, and real-world impact.
April 8, 2026
by Otavio Santana DZone Core CORE
· 4,914 Views · 1 Like
article thumbnail
Serverless Glue Jobs at Scale: Where the Bottlenecks Really Are
At scale, Glue jobs become shuffle-bound, not CPU-bound. Skew and file strategy dominate runtime. Adding workers helps less than reshaping the workload.
March 13, 2026
by Vivek Venkatesan
· 5,276 Views · 2 Likes
article thumbnail
AI Is Rewriting How Product Managers and Engineers Build Together
AI breaks the traditional handoff between product and engineering. Success will depend on how PMs and engineers share tradeoffs around cost, latency, and risk.
March 10, 2026
by Raman Aulakh
· 3,599 Views · 2 Likes
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • ...
  • Next
  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook
×