Start learning free

LESSON 131 OF 173 · BACKEND: SPEED

Pages That Stay Fast

Practice

A real shop may sell 100,000 products, and no answer should carry them all, so the list comes in pages. With LIMIT and OFFSET, a deep page like OFFSET 90000 makes the database walk past 90,000 rows only to throw them away: the deeper the page, the slower it gets.

A keyset page remembers where the last page ended instead: "the rows after id 6". The id is the primary key, which has its own index, so the database jumps straight to the first id after it, and page 1 and page 30,000 cost the same. Here is the idea for a list of orders, 5 per page:

last, _ := strconv.Atoi(r.URL.Query().Get("last"))
rows := runQuery("SELECT id, status FROM orders WHERE id > ? ORDER BY id LIMIT 5", last)

Each answer also tells the client where the next page starts: "next_after" is the id of the page's last product when the page is full, and null on a shorter, last page. Because a page starts after an id, not after a count of rows, a product removed between two pages doesn't make the next page skip one.

GET /products          →  200  {"next_after": 3, "products": [{"id": 1, "name": "Mug", "price": 12}, {"id": 2, …}, {"id": 3, …}]}
GET /products?after=3  →  200  {"next_after": 6, "products": [{"id": 4, "name": "Pen", "price": 2}, {"id": 5, …}, {"id": 6, …}]}
GET /products?after=6  →  200  {"next_after": null, "products": [{"id": 7, "name": "Stapler", "price": 9}, {"id": 8, …}]}
GET /products?after=8  →  200  {"next_after": null, "products": []}
(then the Notebook, id 2, is removed)
GET /products?after=3  →  200  {"next_after": 6, "products": [{"id": 4, …}, {"id": 5, …}, {"id": 6, …}]}
GET /products          →  200  {"next_after": 4, "products": [{"id": 1, …}, {"id": 3, …}, {"id": 4, …}]}

Your task: add a constant pageSize = 3, and make the GET /products handler read after from the query string (0 when it is missing), fetch the id, name and price of the products whose id is bigger, in id order, at most pageSize of them, and answer {"products": […], "next_after": …} with writeJSONWithETag.


Assignment
Follow the Coding Tutorial to turn GET /products into keyset pages.


Hint
Read the value with strconv.Atoi(r.URL.Query().Get("after")), which gives 0 when it is missing. Start with var nextAfter any, which stays nil (JSON null), and only when the page is full, len(result) == pageSize, set it to the id of the last product, result[len(result)-1].ID.


STEP 1 OF 7
    Ctrl + Enter to check