AI 资讯
Reclaiming Terabytes: How to Cut a Managed Database Bill Without Downtime
Managed databases are the cloud cost line people quietly stop looking at. Compute gets rightsized, storage on the instances gets cleaned, but the RDS, Aurora, or Azure SQL bill just grows, because a database feels too load-bearing to touch. It is not. Here is how I have cut managed database spend without a maintenance window, in the order of least risk to most. The theme throughout: databases give you more no-downtime levers than people assume, and the biggest wins are usually storage and rightsizing, not some exotic re-architecture. Start with the free win: reclaim dead storage Storage is where the surprise terabytes hide, and most of it comes off with zero downtime. Drop what nobody reads. Old audit tables, soft-deleted rows that were never purged, expired sessions, staging data that got promoted to prod years ago. A DELETE in batches plus a purge job is the boring, safe first move. Reclaim space after deletes. On Postgres, deleted rows leave bloat until vacuumed. Run VACUUM (and check pg_stat_user_tables for dead tuples). On SQL Server / Azure SQL, rebuild or reorganize fragmented indexes to reclaim pages. This is where the "reclaimed terabytes" headlines actually come from. Kill redundant indexes. Unused and duplicate indexes cost storage and slow writes. Postgres pg_stat_user_indexes (look for idx_scan = 0 ) and SQL Server's missing/unused index DMVs tell you which ones earn their keep. Dropping an unused index is online. Right-size your storage type. On AWS, moving from gp2 to gp3 lets you provision IOPS and throughput independently and usually costs less for the same performance. The modify is applied without downtime. None of the above requires a window. It is pure hygiene, and on a neglected database it is often the single biggest line-item drop. Rightsize the instance (yes, without downtime) The reflex fear is that resizing a database means an outage. With a Multi-AZ deployment it usually does not. Check if you are oversized first. Pull 30 days of CPU, fre
AI 资讯
Your AI agent writes migrations that look safe. Here's what they actually do to Postgres.
You've seen the headlines by now. An agent in Cursor wiped a company's production database, backups and all, in about nine seconds. Replit's agent nuked another company's prod. Same shape every time: the agent was sure of itself, the SQL was valid, and nobody was in the loop to say wait. Those are the loud failures. The fix for them is boring and you already know it. Don't hand an agent write access to prod. Read-only by default, propose instead of apply, keep a human on the button. But there's a quieter version that a permissions policy won't catch, and that's the one I want to talk about. Your agent is probably doing it right now. It looks completely fine in the diff. The migration that passes review and still takes the site down Ask an agent to make an email column unique. It writes: ALTER TABLE users ADD CONSTRAINT users_email_unique UNIQUE ( email ); Correct SQL. Does exactly what you asked. It sails through review because there's nothing to see. Then on a users table with any real size, it grabs an ACCESS EXCLUSIVE lock and scans every row to build the unique index, and for the whole length of that scan nothing else can read or write the table. The API starts timing out. The connection pool fills. Now you're in an incident over a one-line migration that everybody approved. The agent didn't do anything a decent junior engineer wouldn't have done. That's the trap. The danger isn't the SQL, it's the lock the SQL takes, and you can't see a lock by reading a statement. You'd have to know Postgres locking cold: which DDL grabs which lock, and for how long, and what it shuts out while it holds. And you'll still miss one at 2am. I got tired of missing them. So I measured one. What the lock actually costs I ran the same schema change two ways against a real Postgres 18. Fifty million rows, twenty connections doing ordinary traffic. The unsafe version was a plain SET NOT NULL , which also scans under ACCESS EXCLUSIVE . The safe version was the NOT VALID then VALIDATE da
AI 资讯
SQL Window Functions: How to Get the Top Row Per Group
By the end of this page you can answer the question that stops most people the first week they write SQL: which row is the best one in each category. You will know OVER and PARTITION BY , the three ranking functions and how each treats a tie, a running total, and LAG for comparing a row to the one before it. It is about twenty-five minutes. Here is what to actually do with it. The next time you write GROUP BY genre and get back a best rating without the name attached to it, stop rewriting the GROUP BY . Add ROW_NUMBER() OVER (PARTITION BY genre ORDER BY rating DESC) to the plain query instead, then keep the rows numbered 1. That is the whole move, and it replaces a query most people never get working. The short version: a window function adds a calculated column to each row while leaving every row in place. Grouping collapses rows. A window looks at them. One idea decides everything else on this page, so it gets the picture. Both halves do the same arithmetic over the same four rows, and only one of them still has four rows at the end. The original carries a diagram here. In words: Two panels side by side, each starting from the same stack of four identical row shapes. The left panel is labelled GROUP BY. Its four rows funnel down through a single arrow into one row at the bottom, and the four original rows are shown faded to indicate they are gone from the result. Only one row remains. The right panel is labelled OVER. Its four rows stay exactly where they are, at full strength, and each one gains a small badge on its right hand side holding a number: one, two, three, four. Nothing funnels and nothing is faded. The contrast is the whole idea: the left panel ends with a single summary row and no way to say which original row it came from, while the right panel ends with all four rows still present, each carrying its own calculated value. The worked example is real. Every number on this page comes from a published portfolio project: finding the genuinely overlooked g
AI 资讯
GROUP BY and HAVING: How to Summarize Rows Without Getting a Fake Answer
By the end of this page you can write a summary query and know its answer is real. You will know exactly what GROUP BY does to your rows, which columns you are allowed to select afterwards and why, where WHERE goes, where HAVING goes, and why swapping them is the difference between a finding and a number that means nothing. It is about twenty-five minutes. Here is what to actually do with it. On the next summary query you write, add one line setting a minimum group size before you read the ranking. One line, and it removes the most common way a summary query produces a confident wrong answer. The short version: WHERE filters rows before grouping. HAVING filters groups after. Without a HAVING floor, tiny groups float to the top of every ranking. One idea decides everything else here, so it gets the picture. Grouping happens in the middle of the query, and the two filters sit on opposite sides of it. The original carries a diagram here. In words: A left-to-right pipeline in four stages. Stage one is a column of eight individual row boxes. Stage two is a gate labelled WHERE, through which six rows pass and two are crossed out and stopped. Stage three shows the surviving six rows collapsing into three group boxes, one holding three rows, one holding two rows, and one holding a single row. Stage four is a second gate labelled HAVING, through which the group of three and the group of two pass, while the group holding only one row is crossed out and stopped. The result at the far right is two groups. The picture shows that WHERE acts on individual rows before any grouping exists, and HAVING acts on whole groups after they have been formed, which is why the two filters cannot be swapped. The worked example is real. Every number on this page comes from a published portfolio project: 82,956 games from the Steam catalogue, with review counts, ratings and genres. The queries run against the full dataset at Steam Hidden Gems on GitHub . If SELECT and WHERE are also new, start wi
AI 资讯
SQL Foundations, Start to Finish
By the end of this page you can say, out loud and in your own words, what every core piece of SQL does. What a table and a row really are. The six clauses, and the order they actually run in, which is not the order you type them. NULL , and why it breaks comparisons. Filtering, aggregation, GROUP BY and HAVING . Joins. CASE . Subqueries, CTEs and window functions. Keys and indexes. That list is most of what an analyst job, an interview, and a first real dataset will ask of you. Here is what to actually do with it. Go through once end to end without stopping, just for the shape. Then come back to the retrieval sheet near the bottom, cover the right-hand column, and try to say each answer before you read it. That second pass is where the learning happens, and there is measured evidence for it further down. The short version: SQL is one sentence with six parts, and every part answers a different question about your rows. Learn what each part does and where it runs, and the rest is vocabulary. One idea decides more of your SQL experience than any other, so it gets the picture. You write a query in one order. The database runs it in a different order. Almost every confusing SQL error is that gap. The original carries a diagram here. In words: Two columns of stacked boxes face each other. The left column, headed "you write", lists the clauses in typing order from top to bottom: SELECT, FROM, WHERE, GROUP BY, HAVING, ORDER BY, LIMIT. The right column, headed "it runs", lists the same clauses in execution order: FROM, WHERE, GROUP BY, HAVING, SELECT, ORDER BY, LIMIT. Curved lines connect each clause on the left to the same clause on the right. Six of the seven lines run roughly straight across. One line, the one belonging to SELECT, is drawn in a strong accent color and sweeps steeply downward from the very top of the left column to the fifth position on the right, showing that SELECT is written first but runs almost last, after grouping has already happened. What this page
AI 资讯
Can You Run Hybrid Search on One Database? Yes! Here's How CrateDB Does It
Search has got more powerful, but also more complicated It used to be that database queries were simple enough. Either you had an index, or you didn't, and either way you had some kind of optimizer (cost or rule) that turned your SQL statement into a viable plan for finding and returning your data. We now live in a world where in addition to the Boolean logic of traditional RDBMS queries, we also have: Geospatial queries Full text search queries, using BM25 . Vector Search And if that weren't enough, we have to consider that instead of a traditional application issuing the query, it might be an MCP server, and most importantly of all, the business need might be for two or more of these searches to happen at the same time, on the same data. For example: "An MCP server that uses a single, combined geospatial + full text query to identify towns in Bavaria with castles mentioned in text descriptions." How does CrateDB help with this? CrateDB is one of the limited number of products that not only supports all of these search types but is also capable of storing arbitrarily large quantities of data. This is important, as if you have to hit multiple different database servers to solve your business question, not only is your environment much more complicated, but you risk getting incorrect answers as your multiple databases may be out of sync. In this two-part example, based on our playable IOT Analytics scenario, we will show two things: Using Geo + Text to search a weather/tourism database Using CrateDB as a '360 view' for your MCP server If you want to follow along with this post, the setup is based on our IoT Analytics scenario. You can also just skip this blog post and jump straight to the scenario. Using Geo + Full text to search a weather/tourism database The table we're going to use is called ' German Regions ': CREATE TABLE IF NOT EXISTS demo . german_regions ( region_name TEXT PRIMARY KEY , geo_coords GEO_SHAPE , tourism_info TEXT INDEX USING FULLTEXT WITH ( anal
AI 资讯
How to Test Search Relevance Before You Ship a Ranking Change
You can load-test search latency with a script and a graph. Relevance has no such gauge by default, so most teams ship a new ranking rule, eyeball a handful of queries, and hope nothing important regressed. The fix is a small, boring relevance test suite: a fixed set of queries, human-judged expected results, and a metric you compute the same way every time — so "did this ranking change help?" becomes a number you can diff, not an argument you have in Slack. This post is a build guide. By the end you'll have a judgments file, a scorer that outputs precision@k, MRR, and nDCG, and a before/after comparison you can wire into CI. The examples use Postgres full-text search, but the harness is engine-agnostic — Elasticsearch, Meilisearch, or a vector store all slot into the same shape. Why can't I just load-test relevance the way I load-test latency? Latency is a property of the system. Relevance is a property of the match between a query and what a human expected to see — and that judgment lives outside the database. A commenter on an earlier post about running Postgres search in production put it well: latency can be load-tested, but quality needs query sets, expected result buckets, bad-query examples, and a way to compare changes before shipping a new ranking rule. That's the whole job, and none of it comes for free with your index. The trap is thinking a passing query proves relevance. SELECT ... WHERE tsv @@ query returning rows tells you the index matched. It says nothing about whether the right rows landed in the top 5, which is all a user ever sees. The takeaway: relevance is measured against human judgments, not row counts — so the first artifact you build is the judgments, not the query. Building the golden query set Start with 20–50 real queries. Pull them from your search logs if you have them (the head terms plus a long tail of specific ones), or write them from real user intents if you don't. For each query, mark which documents should come back and how rel
AI 资讯
Your ORM is hiding the line that caused the slow query
I was building a runtime N+1 query detector for Node. The detection part worked on the first afternoon. Getting it to tell you which line of your code caused the problem took considerably longer, and taught me something about how ORMs execute queries that I had not thought about before. This is that story, and the fix. The symptom The detector instruments your database driver. When the same query shape runs many times inside one request, it reports it — along with the file and line that issued it, which is the part that actually saves you time: nplusone 1 finding in GET /orders — 51 queries, 840ms N+1 query 50× SELECT * FROM items WHERE order_id = ? at src/routes/orders.ts:47:38 (loadOrdersPage) 612ms spent here That worked. Then I pointed it at an app using Drizzle and got this instead: N + 1 query 12 × select "id" , "order_id" from "items" where "items" . "order_id" = $ 1 < unknown call site > Detected, counted, and attributed to nothing. Do not theorise. Dump the stack My first instinct was that my frame filter was too aggressive — it skips node_modules , node:internal , and the library's own frames, so maybe it was eating something it should not have. Rather than guess, I printed the whole stack at the exact moment the driver was called: const originalQuery = pg . Client . prototype . query ; pg . Client . prototype . query = function (... args ) { const previous = Error . stackTraceLimit ; Error . stackTraceLimit = 100 ; const stack = new Error (). stack . split ( " \n " ). slice ( 1 ); Error . stackTraceLimit = previous ; console . log ( " FRAMES: " , stack . length ); stack . forEach (( line , i ) => { const mine = ! /node_modules|node:internal/ . test ( line ); console . log ( ` ${ String ( i ). padStart ( 3 )} ${ mine ? " >>> " : " " } ${ line . trim ()} ` ); }); return originalQuery . apply ( this , args ); }; Here is what came back for a single await db.select().from(items).where(...) : FRAMES: 12 0 at Proxy.<anonymous> (.../nplusone/dist/adapters/postgre
AI 资讯
Stop context-switching to manage your distributed SQL infra
I remember the old days of manual scaling. You'd jump into a CLI, check your metrics, realize you needed another node or a capacity adjustment, log into a web console, navigate three layers deep into some proprietary dashboard, and hope you didn't click the wrong thing while trying to find a specific cluster ID. Now we have AI agents. But most people are using them wrong. They treat Claude or Cursor as just better search engines for code, rather than giving them hands. If you're running high-availability workloads on something like TiDB Cloud, the friction isn't in writing the SQL—you already know how to do that. The friction is in the operational visibility: knowing exactly what’s happening across your serverless instances versus your dedicated clusters without leaving your IDE. The Gap Between Code and Infrastructure The reason I spend so much time building things like MCPFusion is precisely because of this disconnect. An LLM might help you write a complex join perfectly, but if it doesn't know whether the target TiDB X instance is actually healthy or which project ID handles your staging environment, it's basically flying blind. You end up copy-pasting JSON blobs from your terminal into the chat window just to give the model context. That's slow, prone to error, and frankly, beneath what modern tooling should look like. This is why we released the TiDB Cloud (Serverless Distributed SQL) MCP server on Vinkius. It closes that loop. What This Actually Does (And Doesn't) Let's be very clear about what this tool allows you to do through an agent like Claude or Cursor. We aren't looking for "magic" here; we want predictable utility. The current implementation focuses on discovery and inspection. In DevOps terms, it provides a controlled read-only view of your topology. Here is what's available: Organization Discovery: You can call list_projects to see everything sitting under your umbrella and pull metadata via get_project . This solves the "what was that project ID ag
AI 资讯
dbt Semantic Layer vs Cube vs AtScale: Choosing an Enterprise Semantic Layer
Three semantic layers, three architectures, three very different bills. All three will define what a metric means. None of them proves an AI agent is allowed to run it. Quick orientation dbt Semantic Layer Cube AtScale Core idea Metrics as version-controlled code Headless API in front of metrics OLAP-style aggregate acceleration Strongest when You want engineering discipline Many apps consume the same numbers Heavy, stable aggregate workloads Modelling Hand-authored YAML Hand-authored data model Hand-authored cubes Cost driver Plan tier + query volume Pre-aggregation builds + compute Quote-based licence + compute Governance Upstream, in the warehouse In front of the API On the cube Each is competent at what it was built for. If your consumers are dashboards and analysts, any of the three will serve you. The question none of them answers An agent doesn't arrive with a metric name. It arrives with an intent in English and has to work out which entities, which grain, which joins, and whether it's entitled to any of it. That exposes two gaps every one of these shares: Undefined intent has no answer. Coverage is whatever someone remembered to model. Business questions don't respect that boundary. Authorisation is checked around the query, not inside it. A filter applied after execution means the data already moved. What to actually evaluate on Ignore feature matrices and score these five: Answer a question nobody modelled, on your schema Show why one join path was chosen over two others Same question, two users with different entitlements — show both SQL statements Ask something ambiguous. Refusal or guess? Reproduce a number from six months ago with the definitions then in force Most evaluations stop at 1. Numbers 3 and 5 are the ones that decide whether the thing ships in a regulated business. The full breakdown — architecture-by-architecture comparison, cost profiles, and the migration implications of each — is here: 👉 dbt Semantic Layer vs Cube vs AtScale: Choosing a
AI 资讯
COUNT in SQL, Explained for Beginners
COUNT looks like the simplest function in SQL, and it is the one that quietly trips up the most people in interviews and on the job. The confusion is almost always the same: COUNT(*) , COUNT(column) , and COUNT(DISTINCT column) look nearly identical but count three different things. Once you can say out loud what each one counts, a lot opens up. You can verify a data migration, find duplicates, and measure how complete a column is, all with the same little function. This guide is that explanation, with lots of small examples you can copy. The one-sentence version. COUNT(*) counts rows . COUNT(column) counts rows where that column is not NULL . COUNT(DISTINCT column) counts how many different non-NULL values that column has. Everything below is just that sentence, slowed down. The three forms of COUNT and what each one counts Picture one small table, customers , with a region column where two rows were never filled in: id name region 1 Maya North 2 Jordan South 3 Alex North 4 Sam NULL 5 Taylor NULL Now run the three forms on it: SELECT COUNT(*) AS all_rows, COUNT(region) AS rows_with_region, COUNT(DISTINCT region) AS different_regions FROM customers; all_rows rows_with_region different_regions 5 3 2 COUNT(*) = 5. Every row, no exceptions. The * means "the row itself," so NULLs never matter. COUNT(region) = 3. Only the rows where region has a value. Sam and Taylor are skipped because their region is NULL. COUNT(DISTINCT region) = 2. The different values are just North and South . The two Norths collapse to one, and NULL is not counted. The NULL rule that makes them disagree Predict it first. A table has 100 rows. Twenty of them have no email address. What does counting the email column give you? Say the number before you read on. Here is the whole trick in one line: COUNT(*) counts rows. COUNT(something) counts non-NULL values of that something. So the moment a column has any NULLs, COUNT(column) comes back smaller than COUNT(*) . That gap is not a bug, it is informat
AI 资讯
The card said one column. The apply wrote two.
I have been building a thing that lets a language model propose an UPDATE , then executes it for real inside a transaction, measures the actual before and after values, and always rolls back. A human reads the measurement and decides. Only then does anything commit. The pitch is one sentence: what you approve is not the model's description of its SQL, it is what the database did when the SQL ran. Last week I found that the thing showing you that measurement was showing you a subset of it, and had been since the first release. The failure Real output, from @hyuga/llm-safe-sql@0.4.0 installed from npm. One row: name = 'Tanaka' , postcode = '00100' . UPDATE customers SET name='Sato', postcode='00100' WHERE id=1 What this touches customers — Customer records. The postcode is used for billing address and delivery. 1 row would change, across 1 column: name Measured by running the statement and rolling it back id = 1 name: 'Tanaka' -> 'Sato' One row, one column. postcode is not mentioned, and that is correct — it is being assigned the value it already holds, so nothing about it changes. The card is describing the diff accurately. Approve it. Then, before it is applied, somebody else notices the postcode is wrong and fixes it: UPDATE customers SET postcode = '90210' WHERE id = 1 ; Now apply the approved plan: Applied: UPDATE on customers, 1 row(s), at 2026-08-10T09:49:12.049Z. DB now: [{"name":"Sato","postcode":"00100"}] The fix is gone. Zero warnings. The word postcode never appeared on the approval card, never appeared in the audit record, and never appeared in the comparison the tool makes before it commits. One variable doing two jobs The diff was built like this: const changed : string [] = []; for ( const c of Object . keys ( before )) { if ( same ( before [ c ], after [ c ])) continue ; // drop what did not move if ( auto . has ( lower ( c ))) continue ; // drop what the DB maintains itself changed . push ( c ); } That is a correct answer to "what should the card sho
AI 资讯
Building SaarDB, Part 6: How SQL Queries Become Key-Value Operations
In Blog 5, we built a SQL parser. It can take this: INSERT INTO payments VALUES ( 500 , payment_1 , pending , 1 ) and turn it into a struct: InsertIntoTable { TableName : "payments" , ColumnValues : [] string { "500" , "payment_1" , "pending" , "1" }, } But this is still not enough for the storage engine. Our storage engine only knows how to store key-value pairs. It does not know what a table is. It does not know what a column is. It does not know that 500 is an integer, pending is a string, and 1 is a boolean. So, in this post we solve the missing bridge of persisting these in our key-value store. CREATE and INSERT are PUT operations This is the first major realisation. A key-value store is extensible to store literally anything. This is what we have been saying from the first post itself. But now we will be taking actual examples to prove that. CREATE TABLE Example Let's start with the create table example and see what should be the key and the value. Serialisation The key should be something that uniquely identifies the table, which is straightforward enough in this case as the table name . The value becomes everything else except the key, which is the schema of the table. So, in order to store the table name, we can append a reserved keyword as prefix like schema as a unique identifier. The structure of the key becomes _schema:<table_name> . The next question to answer is: How do we store a struct like below into our key value store where the value is always string? CreateTable { TableName : "payments" , ColumnDetails : [] Column { { ColumnName : "amount" , DataType : Int }, { ColumnName : "id" , DataType : String }, { ColumnName : "status" , DataType : String }, { ColumnName : "captured" , DataType : Bool }, }, PrimaryKeyColumnPosition : 1 , } One way is to serialise the entire struct into a string and store that directly. But in that case, deserialisation is a complex logic. JSON or struct serialisation and deserialisation is both space-heavy and compute inte
AI 资讯
Idempotency Keys: Designing APIs That Survive Retries
Every API that sits behind an unreliable network eventually faces the same problem: a client sends a request, the connection drops before the response arrives, and the client has no idea whether the operation happened. Did the payment go through? Did the order get created twice? The client's only safe move is to retry — which means your server needs a story for what happens when the same "create this thing" request arrives more than once. That story is idempotency keys, and getting the details right is more subtle than it first looks. The core idea The client generates a unique token — typically a UUID — once per logical operation, and attaches it to every retry of that operation: POST /orders Idempotency-Key: 7c3fd9a2-df01-4b3e-9a55-1e5f9b6b6d55 {"sku": "WIDGET-1", "qty": 2} The server's job is to guarantee that no matter how many times a request with that key arrives, the side effect (charging a card, creating an order, sending an email) happens at most once, and every retry gets back the same response the original request would have produced. Note what this is not: it is not deduplicating by request body. Two requests with identical bodies but no key are legitimately two different orders for two widgets. The key is what marks them as "the same attempt," not the payload. The naive approach, and why it breaks A common first pass is a table like: CREATE TABLE idempotency_keys ( key TEXT PRIMARY KEY , response_body JSONB , status_code INT ); On each request: check if the key exists, and if so return the cached response; otherwise do the work and insert the result. This looks right and is wrong in a specific way: it has a race condition. Two retries can arrive concurrently (a client that timed out and fired a second attempt while the first was still in flight), both miss the cache check, and both execute the underlying operation. You've now charged the card twice. Making the check-and-do atomic The fix is to claim the key before doing the work, using the database's ow
AI 资讯
A backup you haven't restored isn't a backup
Migrating from MongoDB Atlas to a self-hosted replica set bought us control and cut our bill. It also quietly removed something we had stopped thinking about: Atlas had been taking continuous backups for us the entire time. After the migration, production data for Prochesta lived in /var/db/mongo on a single VPS. No snapshots. No off-box copy. A rm -rf , a bad migration script, or a dead disk would have been the end of it. We had written "backups" as a follow-up task in the migration spec, which is the engineering equivalent of a sticky note on a bank vault. The requirement we actually cared about was narrower than "back up the database". Most real-world data loss at our scale isn't hardware failure — it's a deploy that writes garbage, or someone running an update without a filter. Recovering to last night doesn't help when the damage happened at 14:20 and you noticed at 14:50. We needed to recover to an arbitrary moment , not to a nightly snapshot. The constraint nobody mentions: Community has no $backupCursor We chose Percona Backup for MongoDB (PBM), and immediately hit the limitation that shapes every decision downstream. PBM offers physical backups — fast file-level copies that restore in minutes and barely touch the running server. They work by opening a backup cursor via the $backupCursor aggregation stage. That stage exists in Percona Server for MongoDB and in MongoDB Enterprise. It does not exist in MongoDB Community, which is what the official mongo:8.0 image ships. So on Community, PBM gives you logical backups only: every document read out through mongod , compressed, and shipped off-box. Two consequences, both accepted deliberately rather than discovered later: Backups cost CPU on the primary — and with a single-member replica set there's no secondary to offload the read to. Restores insert documents and rebuild indexes, so restore time grows with data size much faster than backup time does. At our current size that's minutes, not hours. It's also the t
AI 资讯
Stripe Uses Graph Search and State Machines to Automate Database Remediation
The engineering team at Stripe recently described how they automated database incident recovery by modeling their global infrastructure as a graph. Using graph search algorithms together with state machines, the team computes and executes remediation plans automatically. By Renato Losio
AI 资讯
Building a Bulletproof Comment Reply System in Node.js & MongoDB 🚀
When building a nested reply system, most developers worry about deep tree complexity or messy data structures. For Vlox , I took a different approach: keeping things flat, fast, and secure by reusing a single Mongoose schema with smart atomic limits. Here is a deep dive into how I engineered a production-ready, race-condition-safe reply mechanism using MongoDB transactions, strict type sanitization, and automated limits. How It Works 🛠️ User Action: A user clicks the reply icon and submits their reply. The Payload: Vlox's system sends 3 fields via the endpoint /api/v1/reply/comment/post/:id : id : The post ID (passed as a URL parameter). rootCommentId : The ID of the root comment being replied to. reply : The raw text entered by the user. Sanitization & Validation: The incoming reply is instantly converted to a trimmed string. It then passes through two critical validation checks: Existence Check: The reply must exist. (If a malicious actor sends a payload without a body, the string literally evaluates to "undefined" and gets blocked). Length Limit: The reply must be under 201 characters, enforcing the standard comment limit. Atomic Transactions: If the validation checks pass, the system initiates a Mongoose transaction to execute the following steps safely: Permission Check: It verifies if the user has permission to reply by checking the post's status via await schemas.Posts.findOne(hotQueries.find_user_post(id, req.session.userId)); . Creation: If permissions are valid, it creates a new reply. (Fun fact: It reuses the exact same schema as standard comments!) The Reply Schema Structure: The reply object functions just like a normal comment, with two distinct exceptions: It does not contain a repliesCount field. It includes an extra rootId field, which explicitly points to the ID of the root comment being replied to. Concurrency & Caps: To guarantee that a single comment never receives more than 10 replies while simultaneously incrementing the counter, the system r
开发者
DBNavigator – An DataGrip-inspired Database IDE Built with JavaFX
After months of development, I'm excited to share DBNavigator, a cross-platform database IDE that I've been building from scratch using Java and JavaFX. ✨ Current Features ✅ PostgreSQL support ✅ MySQL support ✅ Modern Datagrip-inspired UI ✅ Multi-tab SQL editor ✅ Syntax highlighting ✅ Schema explorer ✅ Query execution ✅ Professional dark theme ✅ Cross-platform (Windows, Linux & macOS) This project has been an incredible learning journey in desktop application development, JavaFX UI design, database connectivity, and IDE architecture. I'm sharing it with the developer community because I'd genuinely appreciate your honest feedback. I'd love to hear your thoughts on: UI/UX design Performance Missing features Overall developer experience Architecture and code quality Any bugs or improvements you notice Whether you're a Java developer, DBA, or someone who works with databases every day, your feedback would mean a lot and help shape the next version of the project. ⭐ If you find the project interesting, please consider giving it a star on GitHub. GitHub: DBNavigator Thank you for taking the time to review it. Every suggestion, issue report, and critique is greatly appreciated! 🙌
AI 资讯
I benchmarked my language against Rust and Zig, and deleted my best number
I have been building machin for a while — a Go-flavored, type-inferred language that compiles through C to a single native binary. It has grown a lot recently, and I wanted to answer the obvious question honestly: does it beat Rust and Zig at anything? It does, at two things, decisively. But the first thing I found was not a win. It was my own benchmark quietly lying to me, and the number it was lying about was the best one I had. The benchmark was measuring the order I ran things in machin's repo has had a bench/native-speed suite for months: four compute kernels — recursive fib, a mandelbrot, a sieve, a big integer loop — written in machin, Rust and Zig, producing byte-identical output, so the timing compares the same computation three ways. The published result claimed machin won the integer loop by 20-25% . That claim also shipped inside machin guide , which is what every coding agent reads to learn the language. When I re-ran it, the margin was gone. Not shrunk — gone. So I read the harness instead of the output: for kernel in kernels : for lang in [ machin , rust , zig ]: for _ in range ( 5 ): # all 5 machin, THEN all 5 rust, THEN all 5 zig time ( binary ) It ran every sample of one language before starting the next. On a laptop that heats up and down-clocks during a three-second kernel, that does not measure the languages. It measures who had the misfortune of running last . Zig always went last. Zig always looked slowest. The fix is four lines — interleave the rounds, rotate who starts each one. Here is what my headline number did: intsum 10^9 before (blocked) after (interleaved) machin 2832 ms 3079.7 ms rust 3764 ms 3223.8 ms zig 3556 ms 3189.7 ms "machin +20-25%" machin +3% = a TIE A 20-25% win became a tie. I deleted the claim from the README and from machin guide . The harness now also refuses to declare a winner inside a 3% band, because the worst run-to-run spread I measured was 41% of the min sample. Calling winners inside that is how benchmarks start
AI 资讯
The Real-Time Fetish: Why You (Probably) Don't Need Streaming
In modern Data Engineering, there is an unspoken fetish for "Real-Time." If you ask any business stakeholder how fast they need their dashboard to update, the default answer will always be: "As fast as possible." This drives well-intentioned engineers to design incredibly complex architectures. We spin up Kafka clusters, implement Flink, and wrestle with latency, late-arriving data, and tumbling windows. All to have data flowing in milliseconds. But the harsh reality is that the vast majority of companies are building Ferraris just to sit in rush-hour traffic. 1. The Actionability Gap (The Golden Question) The biggest mistake when choosing a streaming architecture isn't technical; it's a business mistake. Before implementing real-time pipelines, the only question that matters is: "Does the company have the operational capacity to make a decision in milliseconds?" If you are building a credit card fraud detection system or a live e-commerce recommendation engine, yes, every millisecond counts. But if the data is feeding a financial dashboard that the executive board only reviews during their Monday morning meeting, updating that screen every second is a colossal waste of money and effort. Real-time data has zero value if the human action is batch. 2. The Hidden Complexity and the Cloud Bill Batch processing is forgiving. If a pipeline fails at 3 AM, you trigger a rerun, and by 8 AM, everything is fine. Batch is cheap, predictable, and easy to debug. Streaming, on the other hand, is unforgiving. Handling application state, event duplication (exactly-once semantics), out-of-order events, and sudden traffic spikes requires a senior engineering team dedicated solely to keeping the infrastructure alive. Furthermore, the cloud bill for 24/7 continuous processing is orders of magnitude higher than spinning up your compute clusters on a schedule. 3. "Micro-Batch" Solves 99% of Your Problems There is a perfect middle ground that the hype industry tries to ignore: the micro-ba