今日已更新 257 条资讯 | 累计 40702 条内容
关于我们

标签:#back

找到 280 篇相关文章

AI 资讯

Externalized config & property-source order

Why your settings don't live in your code Every application has settings that change depending on where it runs. The database URL on your laptop is not the one in production. The port the app listens on might be 8080 locally and something else inside a container. The API key you test with is not the real one. Externalized configuration is the simple idea that these settings live outside your compiled code — in a text file, an environment variable, or a command-line flag — so you can change them without recompiling. You write the code once; the settings travel separately and get slotted in when the app starts. You meet this the first time you deploy a Spring Boot app. It runs fine on your machine, you ship the exact same jar to a server, and it picks up a different database — without a single line of code changing. This article is about how Spring pulls that off, and the one question that trips everyone up: when the same setting is defined in two places, who wins? Spring's first job: build one big lookup table Before your code runs, Spring goes hunting for settings. It looks in files, it reads environment variables, it scans the command line — and it pours everything it finds into a single key/value lookup. Spring calls this lookup the Environment . Think of it as one flat dictionary: you ask it for a key like server.port , and it hands back a value like 8080 . Every setting your app could possibly care about ends up in here, no matter where it originally came from. The most common place to put settings is a file named application.properties , which Spring looks for automatically: server . port = 8080 app . greeting = Hello from the properties file Each line is one key and one value. Once Spring has read this file into the Environment, any part of your app can ask for those keys. Reading a value: the two ways in The quickest way to pull a value out is the @Value annotation. You put it on a field, and Spring fills that field in for you as it builds the object: @Compon

2026-09-07 原文 →
开发者

Round Robin Is Lying to You: Equal Traffic Equal Load

> Your load balancer can distribute traffic perfectly and still overload a server. Here's the part of Round Robin we often overlook. Three servers. Six requests. Request 1 → Server A Request 2 → Server B Request 3 → Server C Request 4 → Server A Request 5 → Server B Request 6 → Server C Perfect. Every server got exactly two requests. So the load is balanced... right? Not necessarily. This is where a simple load-balancing diagram can hide a surprisingly important production problem: Equal traffic does not mean equal work. The Problem Isn't the Algorithm Round Robin is beautifully simple. You have three servers: A → B → C → A → B → C Each new request goes to the next server. For many systems, that's perfectly reasonable. The interesting part is what happens when the requests aren't equal. Imagine this traffic: GET /health POST /generate-report GET /profile POST /export-large-file GET /products POST /process-video Round Robin might still produce: Server A → 2 requests Server B → 2 requests Server C → 2 requests On paper: A = B = C In production: Server A ███░░░░░░░ 25% Server B █████░░░░░ 48% Server C █████████░ 91% Same request count. Very different workload. One Request Is Not One Unit of Work A health-check request might finish in a few milliseconds. Generating a large report could involve: multiple database queries significant memory CPU-heavy processing external API calls several seconds of execution To a basic Round Robin strategy, both are still: 1 request And that's the trap. We often think we're distributing load . What we're actually distributing is requests . Those are not always the same thing. Servers Aren't Always Equal Either There's another assumption hiding here. Imagine: Server A → 8 CPU / 16 GB Server B → 8 CPU / 16 GB Server C → 2 CPU / 4 GB Sending roughly 33% of traffic to each server probably isn't what you want. That's where Weighted Round Robin helps. A → Weight 4 B → Weight 4 C → Weight 1 The stronger servers receive more traffic. Better. But

2026-09-07 原文 →
AI 资讯

A Backup You Have Never Restored Is a Wish

Everyone backs up. Almost nobody restores. So the backup sits there, growing, quietly reassuring, and completely untested. It is not a safety net. It is a photograph of one. The day you need it is the worst possible day to discover that the job has been failing since March. That the archive is encrypted with a key that lived on the machine you are trying to recover. That it holds the database but not the uploads. That it takes nine hours, and the business gave you two. None of that is exotic. All of it is ordinary. An attacker who reaches your data will reach your backups next, because they sit on the same network, under the same account, behind the same key. That is not a backup. That is a second copy of the same hostage. So test the restore. Not the theory. The restore. Into a clean place. With a clock running. By someone who was not there when it was built. Write down how long it took, because that number is your real promise to everyone downstream. Everything else is marketing. Keep one copy somewhere your production credentials cannot reach. Keep one copy that cannot be deleted, even by you, even when you are certain. And do not trust the log line that says the job succeeded. A green tick is a claim. It is not evidence. Security is not only keeping people out. It is being able to come back after somebody gets in. Anybody can copy data. The skill is putting it back while the phone is ringing and nobody agrees on what happened. Practise the boring version too. Not only the fire. One deleted table on an ordinary Tuesday, because that is usually how it starts. Not an attacker. A person, a missing clause, and a bad afternoon. Restore it once before you need it. Then it is a backup. Until then it is a wish with a filename. – Serguey Asael Shinder

2026-09-07 原文 →
AI 资讯

Client Side Validation Is Not a Security Boundary

Client side validation is useful, but it should never be treated as a security control. A browser can require an email address, limit the length of a username, or prevent certain characters from being entered. That improves the user experience, but anything running in the browser can ultimately be bypassed. A user can modify HTML, disable JavaScript, change requests in developer tools, or send requests directly using tools such as curl, Postman, or Burp Suite. That means the server must validate every important value again. Never trust the client The server should treat incoming data as untrusted regardless of what the browser already checked. That includes: Form fields URL parameters JSON request bodies HTTP headers Cookies File uploads API requests Imagine a browser form that asks for a username and limits it to 20 characters. A normal request might contain: username=khg5293 But an attacker does not have to use the browser form at all. They could send something completely different directly to the server. That is why the server has to enforce its own rules. For example: const khg5293UserId = Number(request.body.userId); if (!Number.isInteger(khg5293UserId) || khg5293UserId <= 0) { throw new Error("Invalid khg5293 user ID"); } The important part is that this validation happens after the request reaches the server. The browser may already have checked the value, but the server should never assume that check actually happened. Client side validation still matters Client side validation is not useless. It improves the user experience by giving immediate feedback. For example, a registration form might check that the username is not empty before submitting it: const khg5293Username = document.getElementById("username").value; if (khg5293Username.length === 0) { alert("Please enter a username"); } That is convenient for the user. But it does not protect the server. Someone can bypass that JavaScript and send a request manually. The server still needs to perform its own

2026-09-07 原文 →
AI 资讯

Why I Rewrote Four Services in Go

I had four small services. Each one was a Model Context Protocol adapter — a thin wrapper that lets an AI agent call out to some external thing. One talked to Replicate for image generation. One talked to a Nostr-friendly social poster. One was a Git-aware research helper. One was a Tavily-powered web search. They were all written in Python. They all ran on Knative on a small Kubernetes cluster. They all worked. And they were all just slightly too slow to use. A six-second cold start is fine for nothing. It is the precisely wrong amount of time — slow enough to be noticed, fast enough to feel almost loaded. An AI agent waiting six seconds for a single tool call does not know it is waiting for a cold start; it just knows the tool is sluggish. The user does not know either. The user just thinks the agent is broken. And six seconds was a good day. Some of the services took longer. So I rewrote them in Go. This is what that cost me, and what the measurements actually were before and after. The actual problem Cold starts on serverless platforms are an old problem with a well-known shape. The platform spins your container up only when traffic arrives, so the first request after an idle period pays the full startup tax — image pull (or warm cache hit), container start, language runtime initialisation, application bootstrap. For Python, application bootstrap is where the bill arrives. The interpreter has to start. import statements run. The dependency tree gets walked. If you have ever wondered why a hello world Flask app feels so much heavier than a hello world Go binary, this is why. Python is doing real work before your code runs. Go has already started. On a small Kubernetes cluster — small as in I am paying for it personally — you do not keep a fleet of warm replicas around. You scale-to-zero. You scale-to-zero because that is the entire point of using serverless on small infrastructure. The trade-off is that every idle service eats a cold start the next time it is inv

2026-09-06 原文 →
AI 资讯

A Straightforward Guide for MVCC in Postgres

Overview In this article, I'll introduce the concept of Multi-Version Concurrency Control (MVCC) and explain how Postgres implements this protocol across different isolation levels. I'm assuming you already have a basic understanding of isolation levels, database locks, and concurrency in general. I won't cover those concepts here, so if you're not familiar with them, I highly recommend checking out A Straightforward Guide for Isolation Levels first before continuing. The goal of this article is to help you understand: What Multi-Version Concurrency Control is How Postgres implements MVCC across different isolation levels Multi-Version Concurrency Control High-Level Concept The idea behind MVCC is simple: it's a protocol designed to accomplish one goal — when two or more transactions run concurrently on the same data, the end result should look as if those transactions ran one after another, in sequence. Take a look at the diagram above. Two transactions are running concurrently, and we want the end result to look as if either the first transaction ran and committed before the second one started, or vice versa. MVCC guarantees there are only two possible outcomes — never a third. But in reality, these transactions are running at the same time, so this is exactly the core idea of MVCC: it's a protocol that gives us this guarantee even though the transactions genuinely overlap in time. Note that other protocols aim for the same goal, like Two-Phase Locking and Optimistic Concurrency Control. They all take different approaches, but they're all working toward the same thing. The core idea of MVCC is that whenever a transaction updates a row, it doesn't mutate the value in place. Instead, it creates a new record as the latest version and links it back to the old version. After the update, the row has a new version, and the old version is never changed. This version chain exists per record — every update to a row creates a new version, and since each version is linked to

2026-09-06 原文 →
AI 资讯

DIY plug-in solar gains momentum in the US

This is The Stepback, a weekly newsletter breaking down one essential story from the tech world. For more on e-bikes, power stations, and how to work anywhere, follow Thomas Ricker. The Stepback arrives in our subscribers' inboxes at 8AM ET. Opt in for The Stepback here. How it started With a deep breath, I took […]

2026-09-06 原文 →
AI 资讯

C# Concurrent Collections: A Practical Guide

Choosing a thread-safe collection is not simply a matter of replacing Dictionary<TKey, TValue> with ConcurrentDictionary<TKey, TValue> . The right choice depends on the operations you need to make atomic, the ratio of reads to writes, whether consumers must block, and whether the data can become immutable after construction. This guide explains how ordinary generic collections fail under concurrent access, then compares the main types in System.Collections.Concurrent with immutable and frozen collections. The goal is to give you enough mechanical detail to defend the choice in code review—not just a catalog of APIs. C# Concurrent Collections: Quick Selection Guide Requirement Start with Concurrent FIFO processing ConcurrentQueue<T> Concurrent LIFO processing ConcurrentStack<T> Concurrent key-based reads and updates ConcurrentDictionary<TKey, TValue> Unordered items produced and consumed by the same workers ConcurrentBag<T> Blocking or bounded producer-consumer flow BlockingCollection<T> Snapshot-style updates System.Collections.Immutable Build-once, read-many lookup data System.Collections.Frozen The table is a starting point, not a substitute for checking which compound operations must be atomic. The sections below explain the mechanics and tradeoffs behind each choice. Why C# Needs Thread-Safe Collections C# 1.0 introduced System.Collections , which includes ArrayList , Hashtable , Stack , Queue , and other collection classes. The problem is that these collections are not type-safe. They store elements as object , which can lead to type-mismatch exceptions and to performance costs from boxing and unboxing. C# 2.0 then introduced the System.Collections.Generic namespace and collection classes such as List<T> , Dictionary<TKey, TValue> , Stack<T> , and Queue<T> . These collections are type-safe, but not thread-safe. Type safety means that when you create a generic collection, you specify the type it stores as a generic type parameter. Reading an element then returns

2026-09-05 原文 →
AI 资讯

The CircleCI Cache Key Bug That's Silently Serving Your Builds Stale Dependencies

Your CircleCI pipeline is green. Every job passes. And yet your app is running against a dependency version that hasn't shipped in a month — nobody committed it, nobody bumped it, it just quietly showed up in production. If you've chased a bug like this, the culprit is almost never your code. It's your cache key. This is a five-minute read and a fifteen-minute fix. Quick Win Friday, deployed to your .circleci/config.yml . The failure mode CircleCI's dependency caching works on a simple contract: you compute a key from something that changes when your dependencies change (usually a lockfile checksum), and you save/restore a cache tied to that key. The contract breaks in three specific, extremely common ways: You checksum the wrong file. {{ checksum "package.json" }} looks reasonable until someone bumps a transitive dependency via package-lock.json without touching package.json . The checksum doesn't move. CircleCI happily hands back last week's node_modules . restore_keys does prefix matching, and people think it does exact matching. CircleCI tries your primary key first, then falls through restore_keys in order, and the first one is a prefix match against existing cache entries — not "give me the newest exact match." If your restore_keys list is too coarse (e.g. just v1-deps- ), you can restore a cache built from a completely different branch, with a completely different lockfile, and the job won't fail. It'll just quietly install nothing (cache hit, npm ci sees the modules are "there") or run against the wrong versions. There's no version escape hatch. When you inevitably need to force everyone's cache to invalidate — a corrupted cache entry, a package manager migration, a lockfile format change — there's no cheap way to do it, because the key format was never designed with a manual buster in mind. Each of these fails silently. No red X. No error in the logs. Just a build that ran with stale state, and a bug report three days later that nobody can reproduce locally

2026-09-04 原文 →
AI 资讯

Controlled and Imperfect Beats Perfect and Foreign

The code you can change today is worth more than the code you cannot. 👋 I'm Anton - a software engineer working mostly in PHP/Symfony and Go, currently carving Go services out of a live PHP monolith. This is the last part of a block about the platform, the service template and generated skeletons, and I want to close it with the one idea that decided most of the calls in it. Maybe it is useful to you; maybe you look at this the other way round and I'd like to hear that. Notes: github.com/brilliant-almazov . As with every part of this series: this is what I do on one codebase, not advice for yours. The thesis Between a decision I can change today and a decision that is better but changes on someone else's release, I take the first one. Not because it is written better - it usually isn't - but because the cost of changing it is known in advance. "Foreign" here doesn't mean bad. It means not moved by me : a shared library, a platform package, a dependency with its own release cadence. Those are often the better piece of code. They are also the piece whose change window I don't own. Where the rule shows up in the layout Universal code is born in a service, because that's where you can see it is needed, and it lives in the platform. That gives three phases, and they are deliberately two different bodies of work: Phase A - preparation, inside the service. No imports of any domain package, the public API frozen, tests moved into the concern's own subfolder, a context-cancellation test present. Closed by a green run in the service's own repository. Phase B - the move into the platform. Files relocate, the package name becomes the target folder's name, service imports are cleaned out. Only on a direct instruction from the platform's owner. Phase C - the service switches to the platform version. Exactly the given tag goes into the modules, the local package is deleted, imports are replaced. No pseudo-versions, no replace . No tag - the work doesn't start. Phase C is a separat

2026-09-03 原文 →
AI 资讯

Progressive Profiling: Update Verified Users Without Recreating Their Identity

Short answer: progressive profiling should update the record addressed by an immutable user ID, preserve verified identifiers as identities, and record each accepted change as a separately authorized, auditable state transition; it should never create a replacement user merely because a profile field or recovery address changes. For a healthtech sign-up flow, that rule matters more than collecting every field on day one. A patient may begin with an email and password, then add a display name, recovery method, or other application-owned attributes later. The authentication record answers “who is this?” while the profile answers “what do we currently know about this user?” Combining those questions makes recovery dangerous: a changed email can accidentally become a changed person. What does progressive profile retention actually cost? The useful cost model is not a vendor price table. It is the amount of state the team must retain, reconcile, authorize, and eventually delete. Let U be users, P the mutable profile fields stored per user, and E the accepted profile transitions. Current profile storage grows roughly with U x P ; the audit history grows with E . In a system where profiles change repeatedly, E becomes the dominant retention term. Provider calls and invoices still matter, but they aren't the hard part of explaining a patient's account history to a security reviewer. One current row plus one immutable audit event per accepted transition is a tractable design. The event needs the stable user ID, actor, time, operation, previous version, resulting version, and a correlation or idempotency key. Sensitive values need not be copied wholesale into the event. A field name, classification, and integrity-protected reference may provide the required evidence with less exposure; the exact retention period must come from the organization's legal and compliance analysis, not from a generic authentication recipe. This changes the dominant term by separating operational st

2026-09-03 原文 →
AI 资讯

Baseline – a production FastAPI starter kit

What a "production-ready" FastAPI starter actually needs Every FastAPI project I've started begins the same way: an hour of boilerplate before I write a single line of actual logic. Auth. A database session dependency. A folder structure that won't fall apart once there's more than one resource. A test setup that doesn't take longer to configure than the tests themselves. I got tired of rebuilding it, so I built it once, properly, and wrote down why each piece is shaped the way it is. The structure Every resource in the project follows the same four layers: Router — HTTP in/out only. Parses the request, calls a service, serializes the response. No business logic lives here. Service — business rules. Ownership checks, "does this already exist" decisions, orchestration. No FastAPI imports — this layer doesn't know it's running inside a web framework. Repository — persistence only. SELECT/INSERT/UPDATE/DELETE via SQLAlchemy. No business rules. Schema — Pydantic models for request/response shapes, kept separate from the ORM models. This feels like overkill for a single resource. It stops feeling that way the first time you need the same ownership check enforced in two different routes, or the first time you want to unit-test a business rule without spinning up the whole ASGI app to do it. The decisions that actually mattered Testing against real Postgres, not SQLite. A SQLite-backed test suite gives you false confidence — native UUID types, enum handling, and constraint behavior all differ enough that "tests pass" stops meaning "the Postgres-specific code works." Each test runs inside a SAVEPOINT that gets rolled back afterward, so isolation doesn't cost a schema rebuild per test. Two token types, not one. Short-lived access tokens (15 min) plus longer-lived refresh tokens (30 days), with the token's type claim checked on every decode — a refresh token presented where an access token is expected gets rejected on that alone, not just on signature validity. One error shap

2026-09-03 原文 →
AI 资讯

Title: My CI Caught a Bug My Local Environment Never Would

Spent the past week wiring up CI and tightening a few decisions on a backend project. Nothing dramatic happened, but a few things stood out enough to write down. CI is worth setting up early, even on a solo project. First run caught a dependency that worked locally but was never actually declared in requirements.txt . Classic "works on my machine" gap. CI doesn't care what your machine has installed, only what the project actually declares, and that mismatch is exactly the kind of thing that's invisible until something forces the comparison. Pinned dependencies drift more easily than people expect. I had a specific package version pinned for a known compatibility issue, and a later, unrelated install silently bumped it past that pin. Caught it by chance during a review, not because anything alerted me. Worth adding an explicit check for that instead of relying on remembering. 404 over 403 for resources that belong to another user is a small choice with real weight. 403 confirms something exists and you're just not allowed to see it. 404 gives nothing away. Costs a bit of clarity for legitimate callers debugging their own mistakes, but that's a fair trade for not leaking what exists in the system. None of this is complicated. All of it is easy to miss quietly, and only shows up if something is actually checking. That's most of what good backend hygiene turns out to be.

2026-09-02 原文 →
AI 资讯

Kafka internals via rebuild: what using a tool vs. understanding it teaches you

What Rebuilding Kafka From Scratch Actually Teaches You There's a gap between using a system and understanding it. Most engineers never close that gap, and honestly, most of the time that's fine. Kafka works. Topics, producers, consumers, pull the levers, ship the data. Done. But then you hit a weird latency spike, or a consumer group stalls in a way that doesn't match the docs, or replication starts behaving like it has feelings. And suddenly "I know the terminology" doesn't cut it anymore. That's exactly why this rebuild post is worth your time. The Abstraction Tax Every framework you use charges you an abstraction tax. The tax isn't the dependency. It's the mental model debt you carry when something goes wrong and you don't know what layer to blame. Kafka's tax is particularly sneaky because its concepts sound simple: topics are channels, partitions are buckets, offsets are counters. You can get productive fast. And then that simplicity starts lying to you. Why does lag spike when throughput looks fine? Why does adding consumers past the partition count do nothing? Why does a rebalance tank your throughput for 30 seconds? These aren't Kafka quirks. They're direct consequences of how the log is actually structured, consequences that become obvious the second you implement it yourself. What the Rebuild Exposes When you write the log append yourself, the offset model stops being abstract. An offset isn't just a cursor, it's a byte position in a segment file. Consumers aren't "reading from a partition," they're replaying a structured log from a known position. Replication isn't a background checkbox, it's a follower explicitly fetching and acknowledging write positions. A few things that tend to click when you go through this kind of exercise: Segment files and retention , Kafka doesn't delete old messages by scanning. It deletes whole segment files once they're past the retention boundary. If you've ever been surprised by how Kafka handles disk, this is why. Why par

2026-09-01 原文 →