AI 资讯
Your Hand-Rolled Two-Phase Commit Between Two Databases Isn't Atomic
I had a write that spanned two physically separate databases. A rename that had to propagate across several tables in one database and a couple of tables in another, and the two had to stay consistent. No distributed transaction coordinator was available to me. So I did the obvious thing: opened a transaction on each, did the work, and committed them one after the other inside a try/catch with rollbacks on both sides. It felt safe. It compiled. It passed tests. Then I drew the failure on a whiteboard, and the safety evaporated. The window that ruins everything Here's the structure, simplified: await using var txA = await dbA . Database . BeginTransactionAsync (); await using var txB = await dbB . Database . BeginTransactionAsync (); await DoWorkOnA ( dbA ); await DoWorkOnB ( dbB ); await txA . CommitAsync (); // <-- succeeds await txB . CommitAsync (); // <-- what if this throws? Two transactions do not make one atomic operation. CommitAsync is a point of no return, and there are two of them. Between the first commit returning and the second one starting, there is a window. If txB fails in that window — the connection drops, the process is killed, the database hiccups — then A is permanently committed and B never happens. Your rollback in the catch block is useless: you can't roll back txA , it's already durable. The two databases now disagree, and nothing in your code will heal that on its own. This is the dual-write problem , and it's not a bug you can fix by being more careful with try/catch. The atomicity you want simply isn't available from two independent commits. Ordering them, nesting them, wrapping them — none of it closes the window, because the window is inherent to having two commit points. Why "it's never failed" isn't reassurance The seductive thing about this pattern is that the window is small, so in practice it almost never triggers. You can run it for a year and never see an inconsistency. That's exactly what makes it dangerous: it trains you to tr
AI 资讯
Podcast: Increasing Users' Data Agency: From BlueSky's AT Protocol to the Local-First Software Movement
Martin Kleppmann, an associate professor at Cambridge and author of Designing Data-Intensive Applications, discusses the evolution of data systems over the last decade, mainly the shift from monolithic databases to modular building blocks. Kleppmann underlines the importance of moving from cloud-centric data storage systems to decentralised data storage similar to Bluesky’s AT protocol. By Martin Kleppmann
AI 资讯
Article: Governing AI in the Cloud: A Practical Guide for Architects
In this article, the author outlines a practical approach to AI governance in the cloud, covering discovery of shadow AI, data classification at creation, IAM-based enforcement, policy-as-code, and operational controls. The article shows how organizations can embed governance into delivery pipelines, balancing security, compliance, and developer productivity without relying on manual processes. By Dave Ward
AI 资讯
I built a region-survivable system by directing an AI agent. An append-only decision log kept it coherent.
Most of the code in Quorum was written by directing Claude Code, an AI coding agent. That is not the interesting claim, and on its own it is not even a good one. An agent left to run unsupervised produces fast, plausible, locally-correct code that drifts into an incoherent system. The interesting part is the discipline that turned agent speed into a coherent, correct, multi-region database application. That discipline was an append-only architecture decision log. The failure mode of agent-built software An agent has no memory across sessions. It will happily contradict a decision it "made" yesterday, re-open a question that was settled last week, or quietly drift from the design because the local change in front of it looks fine. Each individual output is reasonable. The aggregate, without governance, is a system where the data model fights the access layer and the third change undoes the first. This is the part people underestimate when they talk about AI coding velocity. Speed without a source of truth does not get you to a good system faster. It gets you to entropy faster. A fast writer with no memory and no sense of consequence is a liability at scale unless something outside the agent supplies the continuity. The decision log Quorum carries a file of numbered architecture decisions, DEC-001 onward, now past two dozen. Each entry has the same shape: the context that forced the decision, the decision itself, references to the prior decisions it refines or interacts with, and a status. Three rules make it work: Append-only. Entries are never edited. A later decision can supersede an earlier one, but it does so as a new numbered entry that references the old one. The history of why the system is shaped the way it is stays intact and readable, including the choices that were later reversed and why. Committed separately from code. The decision and the code that implements it are different commits. The log reads as a clean narrative of intent, independent of the diffs
AI 资讯
[System Design] Ride-Hailing Dispatch Algorithm: How Uber DISCO & Grab DispatchGym Match Drivers
Every time you tap "Book Ride," a system makes dozens of decisions in under two seconds: Which driver? What route? What's the real ETA? This article breaks down exactly how the dispatch algorithm works — from the greedy approach that fails at scale, to the bipartite graphs, batched matching, and surge pricing mechanics that power Uber, Lyft, Grab, and Gojek today. Why a Greedy Dispatch Algorithm Fails (Closest Driver Problem) The first instinct when designing a matching system is to pair every customer with their nearest driver. However, this Greedy approach causes massive losses at a system-wide scale: Example: 3 riders (R1, R2, R3) and 3 drivers (D1, D2, D3) Greedy Matching (closest driver): R1 ← D1 (ETA 2 mins) ✓ R2 ← D3 (ETA 8 mins) ← D2 was "taken" by R1, even though D2 is closer to R2 R3 ← D2 (ETA 10 mins) ← Terrible outcome Total ETA: 2 + 8 + 10 = 20 minutes Optimal Matching (global optimal): R1 ← D2 (ETA 3 mins) R2 ← D1 (ETA 3 mins) R3 ← D3 (ETA 4 mins) Total ETA: 3 + 3 + 4 = 10 minutes ← 50% better! Uber refers to this problem as Global Optimization — finding an assignment strategy that minimizes the total ETA of the entire system , rather than optimizing just for individual pairs. Bipartite Graph Matching: The Mathematical Foundation (Lyft) Before diving into the systems, it helps to understand the mathematical model that all ride-hailing matching engines share at their core. Lyft formalizes dispatch as a bipartite graph matching problem : Bipartite Graph: Set A (Riders): { R1, R2, R3, R4 } Set B (Drivers): { D1, D2, D3, D4, D5 } Edges: every possible Rider ↔ Driver pair Edge Weight: cost of that match (e.g., ETA, driver rating, distance) Goal: Find a set of edges (a "matching") where: - No rider is matched to more than one driver - No driver is matched to more than one rider - The total cost of all selected edges is minimized This is known as the Minimum Weight Bipartite Matching problem. The classical algorithm for solving it is the Hungarian Algorithm (
开发者
There Is No Perfect Solution in Software Development: Every Decision is a Tradeoff
Most bad decisions in software engineering aren't made because the engineer chose wrong between two...
AI 资讯
The Disk-Level Architecture of OLTP vs. OLAP
Every backend engineer has seen this happen, you build an application on a relational database like MySQL, handling thousands of concurrent transactions effortlessly. Then, the business asks for a real time analytics dashboard. But when you run an aggregation query over historical data, suddenly the database that effortlessly managed live traffic starts thrashing, evicting your working set, and dragging application performance down. This isn't a tuning problem, a missing index, or a badly written query. It’s a fundamental architectural collision. OLTP (Online Transaction Processing) OLTP encompasses nearly every concurrent digital interaction triggered across a distributed system. A user downloading a PDF, a microservice firing an automatic maintenance log, a comment on a social feed these are all transactions. Data engineers rely on OLTP systems (like MySQL or PostgreSQL) to capture these concurrent streams of interactions for creating , updating and deleting records. The Tree Based In-Place Engine To reliably capture massive volumes of transactions without corrupting data or locking up the application, OLTP systems rely on a highly optimized, row oriented architecture built around the B+ Tree. Because they must provide immediate, atomic updates to existing records, transactional databases manage state through a strict sequence of physical tree traversal and in-memory page mutation: The B+ Tree Indexing: When a transaction reads or updates id: 1, the engine traverses a B+ Tree from the root, through the branch nodes, directly to the specific physical leaf node holding that row. This O(\log n) traversal guarantees a fast, isolated point-lookup. It ensures the application always hits the single version of the row without scanning irrelevant data. The Buffer Pool & In-Place Updates: OLTP systems perform in place updates. The database pulls the exact page containing id: 1 from the physical disk into memory (the Buffer Pool). The specific row is mutated directly in RAM
AI 资讯
Edge Computing in the Browser: How I Replaced a Backend Server with Web Workers & WASM
The obsession with centralizing heavy compute on backend servers is a massive bottleneck for both cost and latency. In 2026, as more applications move to the edge, developers are realizing that the user's browser is an incredibly powerful, untapped compute engine. Recently, I challenged myself to build a free live chess game analyzer for my developer utility suite, CipherKit. The traditional architecture for this requires passing FEN strings to a dedicated backend cluster running the Stockfish engine, which introduces network latency and scales operational costs linearly. I wanted to achieve a 100% client-side, zero-latency experience. Here is how I offloaded the heavy lifting entirely to the browser edge. The Architecture: WASM + Web Workers Running a heavy calculation engine directly in JavaScript instantly blocks the main UI thread. To achieve a flawless 60fps UI, I completely decoupled the state from the computation. The UI Thread: Handles strict DOM rendering, board states, and piece animations. The Worker Thread: Instantiates the Stockfish engine via WebAssembly within the browser's memory. When a live game update occurs, the main thread fires a simple FEN payload via worker.postMessage() . The Worker processes the deep-line evaluations (Depth 20+) asynchronously in the background. It then streams the evaluation lines back to the main thread without causing a single micro-freeze. The Result By treating the browser as the edge compute layer, the tool achieves: Zero Server Latency: Bypassing API rate limits and network bottlenecks. $0 Infrastructure Cost: Heavy compute is crowd-sourced to the user's local device. Absolute Privacy: Sensitive payloads never leave the browser. If you want to see this local asynchronous thread management in action, you can test the live analyzer (and inspect the network tab) here: 👉 CipherKit Live Chess Analyzer Are you offloading heavy computations to the client side in your current projects, or are you still relying on traditional
AI 资讯
Agent-to-Agent Communication Over Email
Your procurement agent needs three quotes for a hardware order. The vendor on the other side runs a sales agent that answers pricing questions automatically. Neither team has talked to the other. There's no shared API contract, no agreed-upon protocol, no integration project. The procurement agent just... sends an email. The sales agent replies. A negotiation happens. That works because both agents have something most AI agents don't: a real email address. The interop problem nobody's protocol has solved The industry is busy designing agent-to-agent protocols — schemas for capability discovery, message envelopes, trust handshakes. All of them share a bootstrapping problem: both sides have to adopt the same spec, and specs only help once everyone you want to talk to has implemented them. Email skipped that problem decades ago. It's federated (anyone can run a mailbox on any domain), it has identity built in (the address), it has conversation state built in (threading), and every organization on earth already accepts inbound delivery. An agent that speaks SMTP can communicate with any counterpart — human or machine — without anyone agreeing on anything in advance. What each agent needs: a first-class identity Agent Accounts — a beta feature from Nylas — give an agent exactly that. Each one is a hosted mailbox like procurement-agent@yourcompany.com that sends, receives, maintains folders, and is indistinguishable from a human-operated account to anyone interacting with it over SMTP. Under the hood it's just another grant: you get a grant_id that works with the existing Messages, Drafts, Threads, Folders, Attachments, and Webhooks endpoints. The "indistinguishable from a human account" part matters more than it sounds. It means agent-to-agent and agent-to-human are the same code path. Your procurement agent doesn't care whether sales@vendor.example is a person, a bot, or a person who hands hard questions to a bot. The conversation degrades gracefully to human handling a
AI 资讯
Human-in-the-Loop: Email Approval Workflows for Agents
The most effective safety control for an email agent isn't a better model, a longer system prompt, or a stricter eval suite. It's a draft folder. Here's the setup. Nylas Agent Accounts — currently in beta — are hosted mailboxes your application creates and controls entirely through the API. Each one is a real address with a grant_id that works against the existing Messages, Drafts, Threads, and Folders endpoints, and each mailbox ships with six system folders: inbox , sent , drafts , trash , junk , and archive . That drafts folder is where your approval workflow lives. Full autonomy is a choice, not a default A common pattern for support mailboxes: an LLM drafts replies to common questions, and humans approve the sensitive ones via a webhook flow. The agent handles the boring 80% on its own — password reset instructions, shipping status, "where's the invoice" — and anything touching refunds, legal language, or an angry customer goes through a person first. The threat you're mitigating is mundane: a model that's confidently wrong. Hallucinated discounts, replies to the wrong thread, a tone-deaf response to a complaint. None of these are exotic attacks. They're the everyday failure modes of putting a probabilistic system on an outbound channel, and the mitigation is to put a deterministic gate between "the model wrote something" and "a customer received it." The gate is three API calls The flow: a message.created webhook fires when mail arrives, your classifier decides the risk level, and high-risk replies become drafts instead of sends. Drafts support full CRUD at /v3/grants/{grant_id}/drafts , so the agent creates one like this: curl --request POST \ --url "https://api.us.nylas.com/v3/grants/ $GRANT_ID /drafts" \ --header "Authorization: Bearer $NYLAS_API_KEY " \ --header "Content-Type: application/json" \ --data '{ "subject": "Re: Refund request for order 4821", "body": "Hi Sam, I have processed the refund...", "to": [{ "email": "sam@example.com" }], "reply_to_mess
开发者
Vertica vs VoltDB (Volt Active Data): Key Differences, Use Cases & How to Choose in 2026
If you're building a modern data stack that requires either high-throughput transaction processing or large-scale analytical workloads, you've likely come across both Vertica and VoltDB (now rebranded as Volt Active Data). While both are distributed relational database management systems (RDBMS), they are architected for completely opposite use cases — choosing the wrong one can lead to 10x higher costs, missed latency SLAs, and poor application performance. In this guide, we break down every key difference between OpenText Vertica and Volt Active Data, with practical examples, real-world use cases, and best practices to help you make the right choice for your team. Table of Contents What is OpenText Vertica? What is Volt Active Data (Formerly VoltDB)? Core Differences Between Vertica and VoltDB Real-World Use Cases: When to Pick Which Best Practices & Common Mistakes Conclusion & Key Takeaways References What is OpenText Vertica? OpenText Vertica (formerly Micro Focus Vertica) is a columnar relational DBMS built exclusively for analytical (OLAP) workloads, first launched in 2005. As of 2026, the latest stable version is 26.1, with native lakehouse and Apache Iceberg export support for modern data ecosystems. Core Vertica Architecture Vertica's design is optimized for fast queries across massive datasets: Columnar storage : Data is stored by column instead of row, enabling significantly higher compression ratios and faster aggregation queries that only access a small subset of columns Massively Parallel Processing (MPP) : Query execution and data are distributed across hundreds of nodes for parallel processing Dual deployment modes : Enterprise Mode : Shared-nothing architecture with data stored locally on nodes for maximum performance Eon Mode : Compute and storage separated, using shared object storage (S3, GCS, ADLS) to scale compute independently of storage for cloud workloads Projections : Physical, sorted copies of data optimized for common query patterns (ins
AI 资讯
Query Objects in PHP: Rich Filtering Without Leaking SQL Into the Domain
Book: Decoupled PHP — Clean and Hexagonal Architecture for Applications That Outlive the Framework Also by me: Thinking in Go (2-book series) — Complete Guide to Go Programming + Hexagonal Architecture in Go My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code and other AI coding tools Me: xgabriel.com | GitHub You ship a list endpoint. GET /orders . It returns the customer's orders, newest first. Clean. Then product wants a status filter. Then a date range. Then "only orders over 100 euros". Then pagination. Then sort by total, descending. Six months later the controller signature reads like a tax form, and the repository method that backs it is findByCustomerAndStatusAndMinTotalBetweenDatesOrderedBy(...) with nine parameters, three of them nullable. So you "fix" it. You let the caller pass a Doctrine QueryBuilder into the repository. Or worse: the use case starts assembling WHERE clauses as strings because that was the fastest way to add the next filter on a Friday. Now your application layer knows about table aliases and SQL operators. The domain speaks SQL, and the whole point of having a repository interface is gone. There is a shape that holds: a query object the domain builds, and an adapter that translates it into SQL. The caller describes what it wants in domain terms. The adapter decides how to fetch it. The leak, concretely Here is the version that grows out of control. Every new filter is another nullable parameter. public function search ( ?string $customerId , ?string $status , ?DateTimeImmutable $from , ?DateTimeImmutable $to , ?int $minTotalCents , string $sortBy = 'placedAt' , string $direction = 'DESC' , int $page = 1 , int $perPage = 20 , ): array ; Nine parameters, half of them nullable, two of them ( sortBy , direction ) carrying raw column names that map straight to SQL. Add a filter and the signature grows again. Every caller has to remember positional order. And $sortBy is a column name leaking through the port: t
AI 资讯
A Domain Logger Port: Decoupling From PSR-3 Without Losing Context
Book: Decoupled PHP — Clean and Hexagonal Architecture for Applications That Outlive the Framework Also by me: Thinking in Go (2-book series) — Complete Guide to Go Programming + Hexagonal Architecture in Go My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code and other AI coding tools Me: xgabriel.com | GitHub You open a use case that places an order. Near the top of the constructor, alongside the repositories and the payment gateway, sits a Psr\Log\LoggerInterface . The method body calls $this->logger->info(...) three times. It looks harmless. It is the most common way framework concerns leak back into a domain you spent weeks keeping clean. PSR-3 is a fine standard. Monolog is the default implementation in most PHP projects, and it earns that spot. The problem is not the library. The problem is where you point it. When LoggerInterface is a constructor argument in your application layer, your use case now depends on a package whose surface area you do not control, whose log levels you may not want, and whose context conventions are someone else's. The dependency arrow points the wrong way. What PSR-3 drags in Psr\Log\LoggerInterface is eight level methods plus a generic log() . The level taxonomy comes from RFC 5424 syslog: emergency , alert , critical , error , warning , notice , info , debug . That is a system-administration vocabulary. Your domain does not speak it. When a use case calls $this->logger->warning('payment retry') , you have to ask: is a retry a warning or a notice ? The answer is an infrastructure judgment call wearing a domain costume. The method signature also accepts an arbitrary array $context and a string|Stringable $message with {placeholder} interpolation. None of that is something your application code should be deciding. <?php declare ( strict_types = 1 ); namespace App\Application\Order ; use Psr\Log\LoggerInterface ; final readonly class PlaceOrder { public function __construct ( private OrderRepository $ord
AI 资讯
Retries and Circuit Breakers Belong in the Adapter, Not Your Use Case
Book: Decoupled PHP — Clean and Hexagonal Architecture for Applications That Outlive the Framework Also by me: Thinking in Go (2-book series) — Complete Guide to Go Programming + Hexagonal Architecture in Go My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code and other AI coding tools Me: xgabriel.com | GitHub You open a use case that places an order. It loads a customer, builds the order, charges a payment gateway, saves, publishes an event. Clean. Then a sprint ago someone added a retry loop around the charge call, because the gateway flaps under load. Now the use case has a for ($i = 0; $i < 3; $i++) , a usleep() , and a comment that says // gateway is flaky on Mondays . The business rule is buried under retry plumbing. A reader who wants to know what placing an order means has to skip past sleep timers and exception counters to find it. Worse, the next person who adds a second outbound call copies the loop. Soon every call to the network has its own hand-rolled retry, each with a slightly different backoff, none of them tested. The use case learned about transient failure. It should never have. Transient failure is not a business rule A use case answers one question: what does the application do when this thing happens? Place an order. Cancel a subscription. Issue a refund. Those are decisions the business cares about. "The payment gateway returned a 503 and we should try again in 200ms" is not a business decision. It is a property of the network between your process and theirs. The domain does not know the gateway is HTTP. It does not know there is a network at all. It asked a port to charge a customer, and the port either succeeds or raises a domain exception. Here is the port, stated in domain language: <?php declare ( strict_types = 1 ); namespace App\Application\Port ; use App\Domain\Customer\CustomerId ; use App\Domain\Shared\Money ; interface PaymentGateway { public function charge ( CustomerId $customerId , Money $amount , s
AI 资讯
Persisting One Aggregate Across Multiple Tables, ORM-Agnostic
Book: Decoupled PHP — Clean and Hexagonal Architecture for Applications That Outlive the Framework Also by me: Thinking in Go (2-book series) — Complete Guide to Go Programming + Hexagonal Architecture in Go My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code and other AI coding tools Me: xgabriel.com | GitHub You have an Order . It owns line items and a shipping address. In the database that is three tables: orders , order_items , order_addresses . The aggregate is one thing in the domain and three rows-with-children in storage. Now look at what most codebases do with that. The service loads the order, loops the items, saves each one in its own statement. Halfway through, a unique-constraint violation throws. The order header is already committed. Two items are in. One address is missing. You have a row in orders that no part of the system considers valid, and no single place to point at when you ask how it got there. The aggregate is supposed to be a consistency boundary. Saving its parts in separate, independently-committing operations breaks that boundary on the way to disk. This post is about closing it: one repository, one transactional save across every table, and a read path that rebuilds the whole object from rows without leaking the ORM into the domain. The aggregate the domain sees The domain class has no idea it lives in three tables. It holds its children directly and guards their invariants. <?php declare ( strict_types = 1 ); namespace App\Domain\Order ; use App\Domain\Shared\Money ; final class Order { /** @var list<LineItem> */ private array $items ; private function __construct ( private readonly OrderId $id , private readonly CustomerId $customerId , array $items , private Address $shipTo , private OrderStatus $status , ) { if ( $items === []) { throw new InvalidOrder ( 'order needs an item' ); } $this -> items = array_values ( $items ); } public static function place ( OrderId $id , CustomerId $customerId , array $it
AI 资讯
Modeling Money in PHP: Your Own Value Object vs brick/money Behind a Port
Book: Decoupled PHP — Clean and Hexagonal Architecture for Applications That Outlive the Framework Also by me: Thinking in Go (2-book series) — Complete Guide to Go Programming + Hexagonal Architecture in Go My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code and other AI coding tools Me: xgabriel.com | GitHub Open a PHP REPL and type 0.1 + 0.2 . You get 0.30000000000000004 . Now imagine that drift sitting in a total column on an invoice. The bug report does not say "IEEE 754 rounding." It says "the customer was charged one cent too much and finance wants to know why." Money is not a number. It is an amount paired with a currency, and it follows rules a float does not know about. You cannot add 10 USD to 10 EUR. You cannot store 19.99 in a binary fraction and expect it back. You cannot round a third of a cent without deciding how to round it, and that decision belongs to your business, not to your storage engine. This post builds a small Money value object that gets the currency safety right, then shows when to stop hand-rolling and wrap brick/money behind a domain port instead. The point of the port is the same point as every port: the rounding rules live in your domain, and the library stays swappable. Why a float is the wrong type Three separate problems hide inside float $amount . The first is representation. A float is a binary fraction. Decimal amounts like 0.10 have no exact binary form, so they round on every store and load. Sum a few thousand line items and the error compounds into real cents. The second is the missing currency. A bare number cannot tell you whether 1000 means ten dollars or one thousand yen. Two amounts in different currencies are not comparable, but the type system lets you add them anyway. The third is the rounding rule. When you split 10.00 across three line items, you get 3.333... per item. Someone has to decide who absorbs the leftover cent. A float makes that decision silently and inconsistently. The fix
AI 资讯
Hand-Rolled Mappers vs AutoMapper: Keeping the PHP Domain Pure at the Boundary
Book: Decoupled PHP — Clean and Hexagonal Architecture for Applications That Outlive the Framework Also by me: Thinking in Go (2-book series) — Complete Guide to Go Programming + Hexagonal Architecture in Go My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code and other AI coding tools Me: xgabriel.com | GitHub You add a column to a table. discount_cents , nullable, default zero. A small migration, a five-minute ticket. The next morning a teammate pings you: orders placed overnight have a discount of null where the domain expected zero, and the total calculation now throws a TypeError deep inside a value object. You trace it back. A reflection-based mapper copied the new column straight onto a property the domain class did not declare, then the next read pulled it back as null and handed it to Money . Nobody wrote the line that connected discount_cents to the domain. The mapper guessed, and the guess leaked the table's shape into a class that was supposed to know nothing about tables. This is the case against automatic mapping at the persistence boundary. A hand-rolled mapper is more typing. It is also the seam that stops the database schema from reaching into your domain. What the boundary is for In a hexagonal layout the domain Order knows about line items, currencies, and the rule that an order needs at least one item. It does not know there is a orders table with a discount_cents column. The repository adapter is the only place those two worlds touch, and the mapper is the translator that stands at that seam. A reflection or annotation-driven mapper collapses the seam. It reads the persistence shape, walks the domain object's properties, and copies whatever names line up. When the two shapes match, it feels free. When they drift, the drift travels silently across the boundary you built it to protect: a renamed column, a new field, a type that does not round-trip. The whole point of the adapter is that the domain and the table can cha
AI 资讯
Beyond the Happy Path: Lessons in Resilience and Distributed State
Reflecting on two major technical challenges from my backend engineering internship, focusing on fault tolerance, infrastructure, and distributed architectures. Introduction As I wrap up my HNG internship, I’ve been reflecting on the gap between code that "works on my machine" and code that survives in production. Here is a look at two tasks from Stage 9—one solo, one team-based—that completely changed how I approach backend engineering and infrastructure. The Individual Task: Background Job Scheduler What it was For my individual Stage 9 task, I built a distributed background job scheduler backed by PostgreSQL and a FastAPI backend, featuring a vanilla HTML/CSS/JS frontend. It manages async tasks (like a mock email sending queue) using a MinHeap priority queue, Directed Acyclic Graph (DAG) dependency resolution, a Dead-Letter Queue (DLQ), and a real-time Server-Sent Events (SSE) dashboard. The problem it was solving Heavy asynchronous tasks—like email generation or batch processing—cannot block the main API thread. The system needed to successfully queue, prioritize, retry on failure, and track every job entirely independently from the standard request-response cycle. How I approached it I built the core logic from the ground up: a MinHeap and an alternative Timing Wheel algorithm for scheduling, a worker engine featuring a 3-attempt backoff sequence (1s, 5s, 25s with jitter), a DAG dependency checker, and a starvation daemon to prevent tasks from hanging. Once the CRUD API and SSE streaming were hooked up, I containerized the entire application with Docker and wrote my deploy scripts. I thought I was done. What actually broke and how I fixed it The application code took hours. The deployment took a full day of non-stop debugging across multiple cloud providers. Oracle Cloud was out of capacity on every free tier shape, and GCP demanded upfront payment. I finally got a t3.micro running on AWS, but that’s when the real DevOps nightmare began: The SSL Chicken-and-Egg
AI 资讯
Why my first RAG layer starts in Postgres, not in a standalone vector database
When people say they are "adding RAG" to a workflow, the conversation often jumps too quickly to infrastructure choices. Should this use a vector database? Should there be a reranker? Should everything go into a knowledge graph? Those are valid questions, but they are usually not the first question. The first question is narrower: What approved knowledge should the workflow be allowed to retrieve before an AI decision happens? That is why my first retrieval layer for operational AI workflows starts in Postgres, not in a standalone vector database. The Workflow Problem In operations-heavy systems, the model usually should not answer from raw memory or from a giant prompt dump. The useful context already exists somewhere else: approved response rules; handoff criteria; product or service notes; source or campaign guidance; operational decisions that were already made by humans. The hard part is not generating fluent text. The hard part is retrieving the right approved context, showing which source influenced the decision and refusing when no safe source exists. Why Postgres First For this kind of workflow, most of the surrounding data is already relational: leads or conversations; workflow names; stages and owners; human review outcomes; source metadata; trace logs; document versions. So the first technical choice is not "where do vectors live in the abstract?" It is: Where can I keep retrieval close to the operational data model? Where can I log the retrieval path and the final decision together? Where can I evolve the schema without creating a second system too early? Postgres plus pgvector is a good first answer to that set of questions. It lets me keep: documents and chunks; metadata such as allowed use and approval requirements; retrieval traces; cost estimates; human review outcomes in one place. What The First Version Needs The first version does not need to be broad. It needs to be inspectable. My narrow retrieval scope looks like this: approved response rules
AI 资讯
AI Agent Architecture: Why Process-Level Resilience Beats Proxy Gateways
The Great AI Architecture Debate When building reliable AI agents, there are two dominant approaches. Approach A: Proxy Gateway (LiteLLM, Braintrust, etc.) App sends request to Gateway Proxy which forwards to LLM Provider. Requires Docker, database, operations team. Approach B: Embedded SDK (NeuralBridge) App plus SDK sends directly to LLM Provider. One dependency, pip install. The Hidden Cost of Gateways Every proxy gateway adds 30-200ms of network latency per call. For an agent that makes 10 LLM calls, that is 300-2000ms of unnecessary overhead. Latency breakdown: Gateway overhead: +30-200ms per call Docker infrastructure: +1-3 GB RAM Database operations: +PostgreSQL maintenance Ops overhead: +0.5 FTE Why Embedding Wins Embedded reliability eliminates the network hop: Factor Gateway Embedded SDK Added latency 30-200ms ~0ms Dependencies Docker, DB, Redis 1 (httpx) Install size 500MB+ 375 KB Single point of failure Yes (proxy) No Ops cost High Zero The Hybrid Reality Gateways serve a purpose for centralized logging, auth, and rate limiting. But for latency-sensitive AI agents, embedding reliability directly in the process is strictly better. The ideal stack: embedded SDK for reliability plus lightweight observability layer on top. https://github.com/hhhfs9s7y9-code/neuralbridge-sdk NeuralBridge: Apache 2.0, 1 dependency, 375 KB.