De 9,3 segundos a 12 ms: el stock en tiempo real con 1 millón de filas — Transcript
Full transcript
- 0:00A cashier types "hammer," hits check
- 0:03stock, and the spinner freezes for 9.3
- 0:05seconds.
- 0:07Today, that same register responds in 12
- 0:09milliseconds using the same server, and
- 0:11the error that froze it isn't what
- 0:13you're thinking.
- 0:14The table has a million inventory
- 0:16records, a log of every incoming and
- 0:19outgoing item. A million rows can fit on
- 0:21a single Excel sheet. How does that
- 0:23freeze a cash register?
- 0:25Same thing in the terminal: 8.4 seconds
- 0:28and nearly 2 GB of memory to return one
- 0:30number.
- 0:31The obvious explanation is that the
- 0:32server is too small. False. Buying a
- 0:34more expensive one doesn't fix it. The
- 0:36error is in a line of code that almost
- 0:38all of us write first. In development,
- 0:41with 50 products, everything flies, no
- 0:43one notices the problem, and that is
- 0:44exactly where the error is born. Think
- 0:46about what is at stake. A register that
- 0:49freezes for 9 seconds means a line of
- 0:51customers, a cashier who loses faith in
- 0:53the system, and an owner who ends up
- 0:54writing stock in a notebook.
- 0:56The code that almost all of us write
- 0:58first is this. Fetching all records with
- 1:01the ORM—the layer that turns rows into
- 1:03objects—and summing inputs and outputs
- 1:05with a for loop. Look at the select line
- 1:08, that's where the problem is, and it
- 1:09isn't obvious at first glance: it
- 1:11doesn't filter anything. It asks the
- 1:12database for the full million rows and
- 1:14converts them into a million Python
- 1:16objects. Then, Python does the filtering
- 1:19by product, row by row, just to keep a
- 1:21few.
- 1:22The result: 8.4 seconds and 1945 MB of
- 1:26RAM.
- 1:27Excessive memory consumption to obtain a
- 1:29single number. But surely you're
- 1:32thinking, "I use pandas, that's faster.
- 1:34" Using pandas to sum inputs and outputs
- 1:36looks more elegant.
- 1:384.1 seconds—better, yes—but you are
- 1:41still moving 1 million rows across the
- 1:43data network and the application, plus
- 1:451116 MB of RAM.
- 1:47And that unnecessary transfer is
- 1:49repeated with every sale. And the lesson
- 1:52is simple.
- 1:53The database is built for calculating.
- 1:56Python is built for coordinating. This
- 1:58is what the same query looks like with
- 2:00the sum inside PostgreSQL. Same stock:
- 2:03326,582
- 2:05units, 34 milliseconds, and a single row
- 2:09travels across the network. From 9
- 2:12seconds to 34 milliseconds with one
- 2:14query.
- 2:15Problem solved, it would seem. Not yet,
- 2:18because 34 milliseconds for a single
- 2:20product is a lot, and the database
- 2:22doesn't say what it spends them on.
- 2:25To know, you don't guess—you ask.
- 2:28The movements table stores every input
- 2:30and every output.
- 2:32A million inventory movements for 5,000
- 2:34products. Run an explain analyze on the
- 2:37same query.
- 2:38The plan says Parallel Seq Scan,
- 2:40sequential scan.
- 2:43It checked a million rows, discarded
- 2:45almost all of them, and kept 144 rows
- 2:48for product 42, 27.8 milliseconds.
- 2:51The number of discarded rows says it all
- 2:54: 333,000 for each process. And there
- 2:57are three processes; nearly 1 million
- 2:59rows read and thrown in the trash to
- 3:01return just a handful. It's like looking
- 3:04for a screw by checking every box in the
- 3:06warehouse, one by one, while the
- 3:08customer waits at the counter.
- 3:10Filtering by product and date without
- 3:12the proper index forces the engine to
- 3:14scan the entire table for every sale.
- 3:16Every sale. With 50 registers, that's 50
- 3:19full scans at once. The first fix is the
- 3:22correct composite index. Order matters.
- 3:25First the product, because it's what you
- 3:27always filter by equality. Then the date
- 3:29, because it's the range. If you reverse
- 3:31it, the engine traverses almost the
- 3:33entire index looking for your product.
- 3:35And with "include," we store the type
- 3:37and quantity inside the index so we
- 3:38don't even have to touch the table.
- 3:41Creating the index on 1 million rows
- 3:43took 400 milliseconds, less time than it
- 3:45takes for your coffee to cool down. Look
- 3:47at what happens. And the drop in
- 3:49response time is immediate. Index only
- 3:52scan. From 27.8 milliseconds to 0.18.
- 3:56That works because vacuum analyze lets
- 3:58the engine know that the pages are clean
- 4:00, so it can read only the index without
- 4:03opening the table.
- 4:04And if the video ended here, it would be
- 4:07just another "add an index" tutorial,
- 4:09but look at something almost no one
- 4:11tests.
- 4:11That product 42 is just any random
- 4:14product. Now test it with the hammer,
- 4:16the best-selling product. 200,000
- 4:18movements, 1/5 of the entire table. With
- 4:21the perfect index, the plan for that run
- 4:24is still at 28 milliseconds. 28.
- 4:27It gives you the same number as before
- 4:29the index, because the index finds the
- 4:31movements quickly, but it still has to
- 4:33sum 200,000 every time someone goes to
- 4:35checkout.
- 4:36Today it's 200,000. If your business
- 4:38doubles its sales, it will be twice as
- 4:40much, and the query grows along with
- 4:42your business. The better you do, the
- 4:44slower it responds. This is where the
- 4:46index, which seemed like the final
- 4:48answer, falls short.
- 4:50Even with indexes, summing thousands of
- 4:52historical entries and exits for a
- 4:54high-turnover product is still slow, and
- 4:56it's exactly what every register queries
- 4:58at the same time.
- 4:59There is the error that almost no one
- 5:01sees. It wasn't the query; it was the
- 5:03question. Why recalculate the entire
- 5:05history every time if history doesn't
- 5:07change? That's how accounting does it.
- 5:10We implement a balance or snapshot table
- 5:12, a product, a cutoff date, and the
- 5:15balance frozen up to that day. They are
- 5:17daily or monthly balance closures, just
- 5:20like in accounting. With a single
- 5:22command, 168,000 monthly cutoffs were
- 5:25created in 1.2 seconds.
- 5:27In production, a scheduled job performs
- 5:29that closure on the first of every month
- 5:31.
- 5:32The trick is to calculate inventory by
- 5:34adding only the movements that occurred
- 5:36after the last closed cutoff. Not the
- 5:39million, just what's recent. This is the
- 5:41function. It queries the last snapshot
- 5:44and adds the recent difference. First
- 5:46the last closed balance, then only the
- 5:49movements since that date. The rest
- 5:51isn't even touched.
- 5:53The query no longer loads the entire
- 5:55product history, it only loads what's
- 5:56recent.
- 5:58The balance doesn't get outdated as long
- 6:00as you follow one rule. The cutoff only
- 6:02freezes the closed past. Everything that
- 6:04happens after the cutoff is added live,
- 6:07so a sale from a minute ago appears
- 6:09immediately. The price is a scheduled
- 6:11task that closes each month and one
- 6:13clear rule. What's closed isn't edited,
- 6:15it's corrected with a new movement.
- 6:18The hammer, now measured over 30 queries
- 6:20and with the median; the 28 from before
- 6:23were a single explain run. The
- 6:25historical sum with an index, 14.7
- 6:28milliseconds; the cutoff balance, 0.2.
- 6:31The exact same stock, 65 times faster.
- 6:35If this type of video with measured
- 6:37numbers rather than theory helps you,
- 6:39subscribe. There is more of this here
- 6:41every week. But a quick query in a
- 6:43terminal is not a real cash register.
- 6:46On a Saturday at 6 p.m., one customer
- 6:48doesn't walk in; 50 walk in at once and
- 6:50they all ask for the hammer. That is
- 6:52where systems truly crash. What matters
- 6:54is what happens when all the registers
- 6:57arrive at once. A nice average in an
- 6:59individual test proves nothing. For that
- 7:02, a load test simulating 50 simultaneous
- 7:04requests with Locust.
- 7:0650 registers. Each one queries the
- 7:09hammer every half second. It is exactly
- 7:11what a Saturday afternoon does to your
- 7:13system. With everyone asking for the
- 7:14same thing at the same time. The live
- 7:17benchmark, first without an index. On
- 7:19the graphs, response times skyrocket and
- 7:21the CPU has almost no room to breathe.
- 7:24Now, afterwards, cutoff balance, the
- 7:26same 50 registers. The times hit the
- 7:28floor and there isn't a single failure.
- 7:31The comparison of the numbers. A
- 7:32clarification, the median is what the
- 7:34typical customer experiences. The worst
- 7:365%is the one that complains out loud.
- 7:39Without an index, a median of 910
- 7:41milliseconds, the worst 5%at 1100, and
- 7:44the CPU at 92%.
- 7:47With the CPU like that, one more
- 7:48checkout and the whole system queues up
- 7:50.
- 7:51The cashier sees the line grow and the
- 7:53customer sees a spinner with a 240ms
- 7:56index, 350 in the worst case, and 51%
- 7:58CPU.
- 8:00With a running balance, a 5ms median, 16
- 8:03in the worst case, and CPU at 1%.
- 8:07Same server, same million rows. The CPU
- 8:10went from 92 to one. That means you no
- 8:12longer need a more expensive server.
- 8:14Your current one is more than enough,
- 8:16with room for other queries. And so the
- 8:18checkout from the beginning returns. The
- 8:20same cashier, the same hammer, 12
- 8:22milliseconds including browser network
- 8:25time. Of course, three best practices so
- 8:27a database doesn't freeze in a busy
- 8:30business. Let the base calculate, let
- 8:32the index respect your filter order, and
- 8:35keep a running balance for what's
- 8:37queried all day.
- 8:38Between one checkout and the other, the
- 8:40hardware is the same.
- 8:42What changed is our understanding of
- 8:43what the database does with every
- 8:45question we ask it.
- 8:46The customer no longer watches the
- 8:48spinner; they watch the cashier handing
- 8:50them the product. But there is a trap
- 8:52worse than a slow query. You can have
- 8:54the checkout at 12 milliseconds and the
- 8:56number on screen can still be wrong. If
- 8:58the logic for entries and exits is
- 9:00poorly designed from the start, the
- 9:02stock on screen never matches the shelf
- 9:04. To make sure that doesn't happen to
- 9:05you, go watch this video I’m leaving on
- 9:07screen. Inventory errors that no one
- 9:09tells you about in the hardware store.
- 9:11There are the most common practical
- 9:13inventory errors, the ones no
- 9:14programming course teaches you. It's on
- 9:17screen. Click it.
About this transcript
This page contains the full transcript of De 9,3 segundos a 12 ms: el stock en tiempo real con 1 millón de filas by Código Espinoza - Automatiza tu Vida, generated from the public captions YouTube serves with the video. The transcript has 1,604 words across 257 segments, with the original timestamps preserved so you can click any line to jump to that moment in the embedded player.
What you can do with it
Use the transcript to take notes, quote the speaker, build a study guide, generate a summary with ChatGPT or Claude via the YouTube Summary tool, or export it as a timed subtitle file with YouTube to SRT. You can also re-open it in the transcriber to choose a caption track available on the video.
Free YouTube transcript tool
YouTube2Text is a free YouTube transcript generator — no signup, no daily limit. Paste any YouTube link and get the full transcript instantly, with timestamps, click-to-jump, caption language selection, AI prompts for ChatGPT, Claude, and Gemini, and exports to TXT, SRT, VTT, or Markdown.