{"id":4421,"date":"2018-01-11T11:56:34","date_gmt":"2018-01-11T19:56:34","guid":{"rendered":"http:\/\/www.couchbase.com\/blog\/?p=4421"},"modified":"2025-06-13T20:20:11","modified_gmt":"2025-06-14T03:20:11","slug":"offset-keyset-pagination-n1ql-query-couchbase","status":"publish","type":"post","link":"https:\/\/www.couchbase.com\/blog\/ko\/offset-keyset-pagination-n1ql-query-couchbase\/","title":{"rendered":"\ub370\uc774\ud130\ubca0\uc774\uc2a4 \ud398\uc774\uc9c0 \ub9e4\uae40: N1QL\uc5d0\uc11c \uc624\ud504\uc14b \ubc0f \ud0a4\uc14b \uc0ac\uc6a9"},"content":{"rendered":"<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-4429\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2018\/01\/arrows2-300x128.png\" alt=\"\" width=\"516\" height=\"220\" srcset=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2018\/01\/arrows2-300x128.png 300w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2018\/01\/arrows2-768x327.png 768w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2018\/01\/arrows2-20x9.png 20w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2018\/01\/arrows2.png 853w\" sizes=\"auto, (max-width: 516px) 100vw, 516px\" \/><\/p>\n<p><span style=\"font-weight: 400\">Read the pagination background in my previous article: <\/span><a href=\"https:\/\/www.couchbase.com\/blog\/optimizing-database-pagination-using-couchbase-n1ql\/\"><span style=\"font-weight: 400\">https:\/\/www.couchbase.com\/blog\/optimizing-database-pagination-using-couchbase-n1ql\/<\/span><\/a><\/p>\n<p><span style=\"font-weight: 400\">Pagination is the task of dividing the potential result into pages and retrieving the required pages, one by one on demand. \u00a0Using OFFSET and LIMIT is the easy way to write pagination into database queries. Together, OFFSET and LIMIT, make the pagination clause of the SELECT statement. Pagination is a common application job and its implementation has a major impact on the customer experience. Let\u2019s look at the issues and solutions with Couchbase N1QL in detail.<\/span><\/p>\n<p><span style=\"font-weight: 400\">Markus Winand from <\/span><a href=\"https:\/\/use-the-index-luke.com\/\"><span style=\"font-weight: 400\">https:\/\/use-the-index-luke.com\/<\/span><\/a><span style=\"font-weight: 400\"> argues it may not be the most efficient. He also suggests, when possible, using keyset pagination approach instead of OFFSET pagination. For this article, I\u2019m going to use the example queries modeled on his article, <\/span><a href=\"https:\/\/use-the-index-luke.com\/no-offset\"><span style=\"font-weight: 400\">https:\/\/use-the-index-luke.com\/no-offset<\/span><\/a><span style=\"font-weight: 400\"> to show what OFFSET optimizations we\u2019ve done, when and how to exploit the keyset pagination in N1QL. <\/span><\/p>\n<p><span style=\"font-weight: 400\">Since Couchbase ships with a travel-sample dataset, we\u2019ll use it to write our pagination queries.<\/span><\/p>\n<ol>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">GET THE FIRST PAGE<\/span><\/li>\n<\/ol>\n<pre class=\"theme:github scroll:true whitespace-before:1 whitespace-after:1 lang:plsql decode:true\">SELECT *\r\nFROM `travel-sample`\r\nWHERE type = \u2018hotel\u2019\r\nORDER BY country, city\r\nOFFSET 0\r\nLIMIT 10;\r\n\r\nCREATE INDEX ixtopic ON `travel-sample`(type);\r\n<\/pre>\n<p><span style=\"font-weight: 400\">For this query, using the index ixtopic, the query engine executes in a simple way. The query engine gets all the qualified keys from the index, then gets all the documents, sorts based on the ORDER BY clause and then drops the OFFSET number of documents (in this case zero) and projects LIMIT (in this case 10) number of documents.<\/span><\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-4430\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2018\/01\/Screen-Shot-2018-01-09-at-10.34.54-AM-300x169.png\" alt=\"\" width=\"744\" height=\"419\" srcset=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2018\/01\/Screen-Shot-2018-01-09-at-10.34.54-AM-300x169.png 300w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2018\/01\/Screen-Shot-2018-01-09-at-10.34.54-AM-1024x577.png 1024w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2018\/01\/Screen-Shot-2018-01-09-at-10.34.54-AM-768x433.png 768w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2018\/01\/Screen-Shot-2018-01-09-at-10.34.54-AM-1536x866.png 1536w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2018\/01\/Screen-Shot-2018-01-09-at-10.34.54-AM-20x11.png 20w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2018\/01\/Screen-Shot-2018-01-09-at-10.34.54-AM-1320x744.png 1320w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2018\/01\/Screen-Shot-2018-01-09-at-10.34.54-AM.png 1558w\" sizes=\"auto, (max-width: 744px) 100vw, 744px\" \/><\/p>\n<p><span style=\"font-weight: 400\">Here\u2019s the plan showing the index and the spans.<\/span><\/p>\n<p>&nbsp;<\/p>\n<pre class=\"theme:github whitespace-before:1 whitespace-after:1 lang:js decode:true\">          {\r\n            \"index\": \"ixtype\",\r\n            \"index_id\": \"8630f5f7e05ee113\",\r\n            \"index_projection\": {\r\n              \"primary_key\": true\r\n            },\r\n            \"keyspace\": \"travel-sample\",\r\n            \"namespace\": \"default\",\r\n            \"spans\": [\r\n              {\r\n                \"exact\": true,\r\n                \"range\": [\r\n                  {\r\n                    \"high\": \"\\\"hotel\\\"\",\r\n                    \"inclusion\": 3,\r\n                    \"low\": \"\\\"hotel\\\"\"\r\n                  }\r\n\r\n<\/pre>\n<p><span style=\"font-weight: 400\">N1QL chooses the index ixtype and pushes the filter (type = \u201chotel\u201d) to the index scan. To implement the ORDER BY clause, it fetches all the documents. In the sort phase, we recognize LIMIT 10 clause and make the sort efficient by keeping only the top 10 items.<\/span><\/p>\n<pre class=\"theme:github whitespace-before:1 whitespace-after:1 lang:js decode:true\">    {\r\n       \"#operator\": \"Order\",\r\n       \"limit\": \"10\",\r\n       \"sort_terms\": [\r\n         {\r\n           \"expr\": \"(`travel-sample`.`country`)\"\r\n         },\r\n         {\r\n           \"expr\": \"(`travel-sample`.`city`)\"\r\n         }\r\n       ]\r\n     },\r\n<\/pre>\n<p><span style=\"font-weight: 400\">Let\u2019s look at the index scan operator efficiency with this index:<\/span><\/p>\n<pre class=\"theme:github whitespace-before:1 whitespace-after:1 lang:js decode:true\">               \"#operator\": \"IndexScan2\",\r\n               \"#stats\": {\r\n                 \"#itemsOut\": 917,\r\n                 \"#phaseSwitches\": 3671,\r\n                 \"execTime\": \"2.646892ms\",\r\n                 \"kernTime\": \"31.095431ms\",\r\n                 \"servTime\": \"19.781593ms\"\r\n               },\r\n\u2026\r\n               \"#operator\": \"Fetch\",\r\n               \"#stats\": {\r\n                 \"#itemsIn\": 917,\r\n                 \"#itemsOut\": 917,\r\n                 \"#phaseSwitches\": 3787,\r\n                 \"execTime\": \"3.43324ms\",\r\n                 \"kernTime\": \"20.847541ms\",\r\n                 \"servTime\": \"69.875698ms\"\r\n               },\r\n\u2026\r\n           \"#operator\": \"Order\",\r\n           \"#stats\": {\r\n             \"#itemsIn\": 917,\r\n             \"#itemsOut\": 10,\r\n             \"#phaseSwitches\": 1849,\r\n             \"execTime\": \"6.519061ms\",\r\n             \"kernTime\": \"88.307572ms\"\r\n           },<\/pre>\n<p><span style=\"font-weight: 400\">There were 917 documentkeys returned by the indexer. The query engine fetched 917 documents. Then the sort (order) operator sorted them and returned the 10 items.<\/span><\/p>\n<p><span style=\"font-weight: 400\">2. GET THE SECOND PAGE<\/span><\/p>\n<pre class=\"whitespace-before:1 whitespace-after:1 lang:plsql decode:true \">SELECT *\r\nFROM `travel-sample`\r\nWHERE type = \u2018hotel\u2019\r\nORDER BY country, city\r\nOFFSET 10\r\nLIMIT 10;\r\n<\/pre>\n<p><span style=\"font-weight: 400\">In this case, everything is same as the query1 except:<\/span><\/p>\n<ol>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">Sort (ORDER BY) operator will return 20 documents (offset + limit).<\/span><\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">The new OFFSET operator will execute after the Order operator and drop the first 10 rows.<\/span><\/li>\n<\/ol>\n<pre class=\"theme:github whitespace-before:1 whitespace-after:1 lang:js decode:true \">         \"#operator\": \"Order\",\r\n           \"#stats\": {\r\n             \"#itemsIn\": 917,\r\n             \"#itemsOut\": 20,\r\n             \"#phaseSwitches\": 1859,\r\n             \"execTime\": \"6.485904ms\",\r\n             \"kernTime\": \"65.92484ms\"\r\n           },\r\n   {\r\n           \"#operator\": \"Offset\",\r\n           \"#stats\": {\r\n             \"#itemsOut\": 10,\r\n             \"#phaseSwitches\": 32,\r\n             \"execTime\": \"5.071503ms\",\r\n             \"kernTime\": \"701ns\",\r\n             \"state\": \"running\"\r\n           },\r\n           \"expr\": \"10\",\r\n           \"#time_normal\": \"00:00.0050\",\r\n           \"#time_absolute\": 0.005071503\r\n         },\r\n<\/pre>\n<p><span style=\"font-weight: 400\">As the OFFSET increases, the number of documents scanned by the sort will increase as well, consuming more memory and CPU.<\/span><\/p>\n<ol start=\"3\">\n<li><span style=\"font-weight: 400\"> Let\u2019s improve the performance by covering the predicate and sort keys with a single index.<\/span><\/li>\n<\/ol>\n<pre class=\"theme:github whitespace-before:1 whitespace-after:1 lang:plsql decode:true \">CREATE INDEX ixtypectcy ON `travel-sample`(type, country, city);\r\nLet\u2019s execute our recent query again:\r\nSELECT *\r\nFROM `travel-sample`\r\nWHERE type = \u2018hotel\u2019\r\nORDER BY country, city\r\nOFFSET 10\r\nLIMIT 10;\r\n<\/pre>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-4435\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2018\/01\/Screen-Shot-2018-01-09-at-2.38.36-PM-300x179.png\" alt=\"\" width=\"641\" height=\"383\" srcset=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2018\/01\/Screen-Shot-2018-01-09-at-2.38.36-PM-300x179.png 300w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2018\/01\/Screen-Shot-2018-01-09-at-2.38.36-PM-20x12.png 20w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2018\/01\/Screen-Shot-2018-01-09-at-2.38.36-PM.png 740w\" sizes=\"auto, (max-width: 641px) 100vw, 641px\" \/><\/p>\n<p><span style=\"font-weight: 400\">It uses the right index and creates the right filters (spans) for the index scan.Explain includes the following.<\/span><\/p>\n<pre class=\"theme:github whitespace-before:1 whitespace-after:1 lang:js decode:true\">       {\r\n           \"#operator\": \"IndexScan2\",\r\n           \"index\": \"ixtypectcy\",\r\n           \"index_id\": \"2a2ed6573354e21\",\r\n           \"index_projection\": {\r\n             \"primary_key\": true\r\n           },\r\n           \"keyspace\": \"travel-sample\",\r\n           \"limit\": \"10\",\r\n           \"namespace\": \"default\",\r\n           \"offset\": \"10\",\r\n           \"spans\": [\r\n             {\r\n               \"exact\": true,\r\n               \"range\": [\r\n                 {\r\n                   \"high\": \"\\\"hotel\\\"\",\r\n                   \"inclusion\": 3,\r\n                   \"low\": \"\\\"hotel\\\"\"\r\n                 }\r\n               ]\r\n             }\r\n           ],\r\n<\/pre>\n<p><span style=\"font-weight: 400\">Let\u2019s look at the index scan operator efficiency with this index:<\/span><\/p>\n<pre class=\"theme:github whitespace-before:1 whitespace-after:1 lang:js decode:true \">               \"#operator\": \"IndexScan2\",\r\n               \"#stats\": {\r\n                 \"#itemsOut\": 10,\r\n                 \"#phaseSwitches\": 43,\r\n                 \"execTime\": \"41.786\u00b5s\",\r\n                 \"kernTime\": \"11.15\u00b5s\",\r\n                 \"servTime\": \"855.759\u00b5s\"\r\n               },\r\n<\/pre>\n<p>&nbsp;<\/p>\n<p><span style=\"font-weight: 400\">Only the required 10 document keys are returned from the index scan! \u00a0The N1QL optimizer evaluates both the WHERE clause and the pagination (OFFSET, LIMIT) clause. The query optimizer decides to pushdown the pagination clause to the indexer based on the order by clause of the query and the index key order. \u00a0<\/span><\/p>\n<p><span style=\"font-weight: 400\">In this case, query predicate is: type = \u2018hotel\u2019<\/span><\/p>\n<p><span style=\"font-weight: 400\">Order by clause is: ORDER BY country, city<\/span><\/p>\n<p><span style=\"font-weight: 400\">Index key order is: (type, country, city)<\/span><\/p>\n<p><span style=\"font-weight: 400\">Just by simple comparison, order by clause is not exactly the same as index key order. However, the leading key of the index (type) has an equality predicate (type = \u2018hotel\u2019). Therefore, the optimizer knows the projected document keys will be in the sorted order of (country and city).<\/span><\/p>\n<p><span style=\"font-weight: 400\">**and the ORDER BY clause to see if it can pushdown the pagination parameters to the index scan. \u00a0In this case, there is a perfect match.<\/span><\/p>\n<p><span style=\"font-weight: 400\">The optimizer pushes both OFFSET and LIMIT in pagination to index scan and the index scan returns only the 10 document keys required no matter the OFFSET! Therefore, fetch only needs to fetch the 10 documents. Indexer does have to go through the index scan to evaluate the keys.<\/span><\/p>\n<p><span style=\"font-weight: 400\">This query executed in about 6.81 milliseconds in my environment. Let\u2019s paginate to subsequent pages to see the performance:<\/span><\/p>\n<table>\n<tbody>\n<tr>\n<td><span style=\"font-weight: 400\">OFFSET<\/span><\/td>\n<td><span style=\"font-weight: 400\">LIMIT<\/span><\/td>\n<td><span style=\"font-weight: 400\">Response Time<\/span><\/td>\n<\/tr>\n<tr>\n<td><span style=\"font-weight: 400\">10<\/span><\/td>\n<td><span style=\"font-weight: 400\">10<\/span><\/td>\n<td><span style=\"font-weight: 400\">6.81 ms<\/span><\/td>\n<\/tr>\n<tr>\n<td><span style=\"font-weight: 400\">20<\/span><\/td>\n<td><span style=\"font-weight: 400\">10<\/span><\/td>\n<td><span style=\"font-weight: 400\">7.17 ms<\/span><\/td>\n<\/tr>\n<tr>\n<td><span style=\"font-weight: 400\">100<\/span><\/td>\n<td><span style=\"font-weight: 400\">10<\/span><\/td>\n<td><span style=\"font-weight: 400\">7.02 ms<\/span><\/td>\n<\/tr>\n<tr>\n<td><span style=\"font-weight: 400\">400<\/span><\/td>\n<td><span style=\"font-weight: 400\">10<\/span><\/td>\n<td><span style=\"font-weight: 400\">9.54 ms<\/span><\/td>\n<\/tr>\n<tr>\n<td><span style=\"font-weight: 400\">800<\/span><\/td>\n<td><span style=\"font-weight: 400\">10<\/span><\/td>\n<td><span style=\"font-weight: 400\">9.08 ms<\/span><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p><span style=\"font-weight: 400\">If you want to paginate in the descending order, change the query and index to the following. The collation of the key in the index should match the collation in the query. In this case, both country and city are in DESCending order.<\/span><\/p>\n<pre class=\"theme:github whitespace-before:1 whitespace-after:1 lang:plsql decode:true\">DROP INDEX `travel-sample`.ixtypectcy;\r\n\r\nCREATE INDEX ixtypectcy ON `travel-sample`(type, country DESC, city DESC);\r\n\r\nSELECT country, city\r\nFROM `travel-sample`\r\nWHERE type = \"hotel\"\r\nORDER BY country DESC, city DESC\r\nOFFSET 10\r\nLIMIT 10;<\/pre>\n<pre class=\"theme:github whitespace-before:1 whitespace-after:1 lang:js decode:true\">\u00a0\u00a0\u00a0\u00a0\u00a0{\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"#operator\": \"IndexScan2\",\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"covers\": [\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"cover ((`travel-sample`.`type`))\",\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"cover ((`travel-sample`.`country`))\",\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"cover ((`travel-sample`.`city`))\",\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"cover ((meta(`travel-sample`).`id`))\"\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0],\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"index\": \"ixtypectcy\",\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"keyspace\": \"travel-sample\",\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"limit\": \"10\",\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"namespace\": \"default\",\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"offset\": \"10\",\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"spans\": [\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0{\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"exact\": true,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"range\": [\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0{\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"high\": \"\\\"hotel\\\"\",\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"inclusion\": 3,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"low\": \"\\\"hotel\\\"\"\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0]\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0],\r\n\r\n\r\n<\/pre>\n<p>&nbsp;<\/p>\n<p><span style=\"font-weight: 400\">Let\u2019s now look at a query that\u2019s not covered by the index. The query we saw earlier, we decide to retrieve the name of the hotel along with country and city. The name isn\u2019t in the index.<\/span><\/p>\n<p>&nbsp;<\/p>\n<pre class=\"theme:github whitespace-before:1 whitespace-after:1 lang:default decode:true \">CREATE INDEX ixtypectcy ON `travel-sample` (type, country DESC, city DESC);\r\n\r\nSELECT country, city, name\r\nFROM `travel-sample`\r\nWHERE type \u00a0= \"hotel\"\r\nORDER BY country desc, city desc\r\nOFFSET 10\r\nLIMIT 10;<\/pre>\n<p><span style=\"font-weight: 400\">In this case, again, the index has the data to cover the WHERE clause and the ORDER BY clause. The index scan will apply the predicate. The index order matches query order (note: there is only equality predicate on leading key. So, country and city are in order automatically). Index skips of the OFFSET number of qualified keys and returns the number of keys specified by the LIMIT clause. The engine retrieves the documents in order to maintain the order specified by ORDER BY clause.<\/span><\/p>\n<p><span style=\"font-weight: 400\">All these little optimizations together ensure the query performance is consistent whether OFFSET is 10, 100 or 1000. The only overhead between higher OFFSET is the index scan skipping over the additional items. Index key evaluation and skipping is significantly faster than getting all the documents and sorting.<\/span><\/p>\n<pre class=\"theme:github whitespace-before:1 whitespace-after:1 lang:default decode:true\">\u00a0\u00a0\u00a0{\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"#operator\": \"Sequence\",\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"~children\": [\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0{\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"#operator\": \"IndexScan2\",\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"index\": \"ixtypectcy\",\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"index_id\": \"b2ab2c276bba7862\",\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"index_projection\": {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"primary_key\": true\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0},\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"keyspace\": \"travel-sample\",\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"limit\": \"10\",\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"namespace\": \"default\",\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"offset\": \"10\",\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"spans\": [\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0{\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"exact\": true,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"range\": [\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0{\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"high\": \"\\\"hotel\\\"\",\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"inclusion\": 3,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"low\": \"\\\"hotel\\\"\"\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0]\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}<\/pre>\n<p><span style=\"font-weight: 400\">Let\u2019s consider a longer running query with a potential of returning up to 24K documents.<\/span><\/p>\n<p><span class=\"theme:github lang:plsql highlight:0 decode:true crayon-inline \">create index idxtypeaiaid ON `travel-sample`(type, airline, airlineid) <\/span><\/p>\n<p><span style=\"font-weight: 400\">Even when the pagination query predicates and order by is completely covered by the index, there is a limited number of cases where the pagination LIMIT and OFFSET can be pushed to the index scan in order to make this very efficient.<\/span><\/p>\n<p><span style=\"font-weight: 400\">Here are general rules and requirements for pushing down OFFSET and LIMIT to the indexer. \u00a0Note these rules are not exhaustive and complete but give you a sense of when this optimization is done.<\/span><\/p>\n<ol>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">The query scan has to be on a single keyspace (Single reference in the FROM clause without JOINs.<\/span>\n<ol>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">FROM `travel-sample` t<\/span><\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">FROM `travel-sample` hotel INNER JOIN `travel-sample` landmarks ON KEYS hotel.lmid<\/span><\/li>\n<\/ol>\n<\/li>\n<\/ol>\n<p><span style=\"font-weight: 400\">Case (a) qualifies, and (b) does not.<\/span><\/p>\n<ol>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">All the predicates in the query are pushed down to index scan (spans).<\/span>\n<ol>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">We cannot push the\u00a0predicates (col LIKE \u201c%xyz\u201d) to index scan. Therefore the pagination cannot be pushed down as well.<\/span><\/li>\n<\/ol>\n<\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">All the predicates should be represented by a single span. If we have to do multiple index scans, post index scan processing (e.g. union scan, distinct scan in the explain), we cannot have the indexer evaluate the pagination. The query will execute without pushing the pagination clause to the index scan. Here are examples of predicate that generate single span.<\/span>\n<ol>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">E.g. (a between 4 and 8 and a &lt;&gt; 12)<\/span><\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">(a = 5 OR a = 6)<\/span><\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">(a IN [4, 6, 9]) <\/span><\/li>\n<\/ol>\n<\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">All the predicates have to be exact.<\/span>\n<ol>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">Exact predicates: (a = 5), (a between 9.8 and 9.99)<\/span><\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">Inexact predicates: (a &gt; 5 and a &gt; $param)<\/span><\/li>\n<\/ol>\n<\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">The query cannot contain aggregation, grouping, having clauses. These do the document reduction after the After the should not eliminate any documents after the index scan (no post index scan filtering\/reduction)<\/span><\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">The ORDER BY keys and the order of the keys should match the index key and index key order.<\/span>\n<ol>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">CREATE INDEX i1 ON t(a, b, c);<\/span><\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">SELECT * FROM t WHERE a = 1 ORDER BY a, b, c;<\/span><\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">SELECT * FROM t WHERE a = 1 ORDER BY b, c;<\/span><\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">SELECT * FROM t WHERE a &gt; 3 ORDER BY b, c<\/span><\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">SELECT * FROM t WHERE a &gt; 3 ORDER BY a, b, c<\/span><\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">SELECT * FROM t WHERE (a LIKE \u201c%xyz\u201d) ORDER BY a, b, c OFFSET 10 LIMIT 5;<\/span><\/li>\n<li style=\"list-style-type: none\"><\/li>\n<\/ol>\n<\/li>\n<\/ol>\n<p><span style=\"font-weight: 400\">The case (2) is a perfect match of the order.<\/span><\/p>\n<p><span style=\"font-weight: 400\">The case (3) doesn\u2019t match perfectly, but the leading key has an equality filter. So, we know the results from the scan will be in the order of (b, c). \u00a0Hence the pagination pushdown to the index is possible.<\/span><\/p>\n<p><span style=\"font-weight: 400\">For the case (4), the leading key has a range predicate (a &gt; 3) and therefore, t pagination pushdown is not possible.<\/span><\/p>\n<p><span style=\"font-weight: 400\">For the case (5), there is a perfect match of the ORDER BY clause to the index keys. Therefore, we can push down the pagination to the indexer, even if there is range predicate on the leading key.<\/span><\/p>\n<p><span style=\"font-weight: 400\">For the case (f), we can exploit the index order, but cannot push down the OFFSET and LIMIT because the complete predicate cannot be evaluated by the index scan. Since we get the results in the required order, after the OFFSET and LIMIT number of documents are projected, we terminate the index scan.<\/span><\/p>\n<p><span style=\"font-weight: 400\">For simplicity, I\u2019ve used one leading key. The statements are generalized and applicable to multiple leading keys.<\/span><\/p>\n<p><span style=\"font-weight: 400\">Obviously, this optimizer logic is complex! See additional information in the appendix on the requirements.<\/span><\/p>\n<p><b>OFFSET overhead:<\/b><\/p>\n<p><span style=\"font-weight: 400\">The initial travel-sample has about 900 documents with (type = \u201chotel\u201d). For experimentation, I simply increase the amount of data by inserting the same data again few times using the following query:<\/span><\/p>\n<pre class=\"theme:github whitespace-before:1 whitespace-after:1 lang:plsql decode:true \">insert into `travel-sample`(key UUID(), value t) select t from `travel-sample` t ;\r\n\r\nselect count(*) from `travel-sample` where type = \u201chotel\u201d;\r\n\r\n{\r\n\r\n\u00a0\u00a0\u00a0\"$1\": 38318\r\n\r\n\u00a0}<\/pre>\n<p><span style=\"font-weight: 400\">Now, let\u2019s try this query:<\/span><\/p>\n<pre class=\"theme:github whitespace-before:1 whitespace-after:1 lang:plsql decode:true\">SELECT *\r\nFROM `travel-sample`\r\nWHERE type = \u2018hotel\u2019\r\nORDER BY country, city\r\nOFFSET 38000\r\nLIMIT 10;<\/pre>\n<table>\n<tbody>\n<tr>\n<td><span style=\"font-weight: 400\">OFFSET<\/span><\/td>\n<td><span style=\"font-weight: 400\">LIMIT<\/span><\/td>\n<td><span style=\"font-weight: 400\">Response Time<\/span><\/td>\n<\/tr>\n<tr>\n<td><span style=\"font-weight: 400\">38000<\/span><\/td>\n<td><span style=\"font-weight: 400\">10<\/span><\/td>\n<td><span style=\"font-weight: 400\">28.97 ms<\/span><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p><span style=\"font-weight: 400\">As the OFFSET increases, the amount of time spent by the indexer to traverse through the index items increases. This affects both latency and throughput. \u00a0<\/span><\/p>\n<p><span style=\"font-weight: 400\">Can we improve this? Keyset pagination comes to the rescue. All the rules we mentioned for pagination optimization apply here as well. In addition, one of the predicates has to be UNIQUE so that we can clearly navigate from one page to another without duplicate documents appearing in multiple pages. With Couchbase, we\u2019re in luck. Each document in a bucket has a unique document key. It\u2019s referenced by META().id in the N1QL query.<\/span><\/p>\n<p><span style=\"font-weight: 400\">The basic insight is, customers typically want the NEXT page instead of some random page from the start of the resultset. So, when you fetch each page in order, remember the last set (HIGHEST\/LOWEST depending on the ORDER BY clause). Then, use that information to set the predicates and position the index for the next scan. This will avoid the wasted key processing during OFFSET.<\/span><\/p>\n<p><span style=\"font-weight: 400\">Let\u2019s look at an example:<\/span><\/p>\n<p><span style=\"font-weight: 400\">CREATE INDEX ixtypectcy ON `travel-sample`<\/span><span style=\"font-weight: 400\">\u00a0(type, country, city, META().id);<\/span><\/p>\n<ol>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">GET The FIRST PAGE. \u00a0Note that I\u2019ve added META().id to the index, projection and ORDER BY clause so it guarantees UNIQUE value in each of them.<\/span><\/li>\n<\/ol>\n<pre class=\"theme:github whitespace-before:1 whitespace-after:1 lang:plsql decode:true\">SELECT country, city, META().id\r\nFROM `travel-sample` use index (ixtypectcy)\r\nWHERE type = \"hotel\"\r\nORDER BY country, city, META().id\r\nOFFSET 0\r\nLIMIT 5;<\/pre>\n<p>Results:<\/p>\n<pre class=\"theme:github whitespace-before:1 whitespace-after:1 lang:js decode:true\">\u00a0{\r\n\u00a0\u00a0\u00a0\"city\": \"Avignon\",\r\n\u00a0\u00a0\u00a0\"country\": \"France\",\r\n\u00a0\u00a0\u00a0\"id\": \"000cb76b-ed85-4f56-9a6e-a6c300902944\"\r\n\u00a0},\r\n\u00a0{\r\n\u00a0\u00a0\u00a0\"city\": \"Avignon\",\r\n\u00a0\u00a0\u00a0\"country\": \"France\",\r\n\u00a0\u00a0\u00a0\"id\": \"0070d2ac-e411-4aeb-a1e7-4a00635d2d5a\"\r\n\u00a0},\r\n\u00a0{\r\n\u00a0\u00a0\u00a0\"city\": \"Avignon\",\r\n\u00a0\u00a0\u00a0\"country\": \"France\",\r\n\u00a0\u00a0\u00a0\"id\": \"01e4c1da-3749-43ce-88e3-37a00d0a376f\"\r\n\u00a0},\r\n\u00a0{\r\n\u00a0\u00a0\u00a0\"city\": \"Avignon\",\r\n\u00a0\u00a0\u00a0\"country\": \"France\",\r\n\u00a0\u00a0\u00a0\"id\": \"0352b5c9-fcbf-48bc-9cf8-f5760cc910a2\"\r\n\u00a0},\r\n\u00a0{\r\n\u00a0\u00a0\u00a0\"city\": \"Avignon\",\r\n\u00a0\u00a0\u00a0\"country\": \"France\",\r\n\u00a0\u00a0\u00a0\"id\": \"038c8a13-e1e7-4848-80ec-8819ff923602\"\r\n\u00a0}<\/pre>\n<p><span style=\"font-weight: 400\">This query ran in 5.14 milliseconds.<\/span><\/p>\n<ol start=\"2\">\n<li><span style=\"font-weight: 400\"> Now, construct the query for next page WITHOUT using OFFSET:<\/span><\/li>\n<\/ol>\n<pre class=\"theme:github whitespace-before:1 whitespace-after:1 lang:plsql decode:true\">SELECT country, city, META().id\r\nFROM `travel-sample` use index (ixtypectcy)\r\nWHERE type = \"hotel\"\r\nAND country &gt;= \u201cFrance\u201d\r\nAND city &gt;= \u201cAvignon\u201d\r\nAND META().id &gt; \"038c8a13-e1e7-4848-80ec-8819ff923602\"\r\nORDER BY country, city, META().id\r\nLIMIT 5;<\/pre>\n<p><span style=\"font-weight: 400\">How did we construct this query?<\/span><\/p>\n<ol>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">Reuse all the predicates in the WHERE clause. (type = \u201chotel\u201d).<\/span><\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">Now, take the keys in the ORDER BY and construct the additional predicates. So, simply take the highest value returned by the last query and \u00a0add the new predicates. \u00a0\u00a0Don\u2019t add the document key yet. \u00a0In this case, we\u2019re sorting in the ascending order. So, use the Greatherthan-OR-Equal-To operator.<\/span><\/li>\n<\/ol>\n<p>In this case, it is: (country &gt;= \u201cFrance\u201d AND city &gt;= \u201cAvignon\u201d).<\/p>\n<p><span style=\"font-weight: 400\">Now, to ensure unique value, we add the META().id predicate.<\/span><\/p>\n<p><span style=\"font-weight: 400\">(META().id &gt; &#8220;038c8a13-e1e7-4848-80ec-8819ff923602&#8221;)<\/span><\/p>\n<p><span style=\"font-weight: 400\">This executes in 5 milliseconds with the following plan. As you go through the subsequent pages, simply repeat the steps to construct the next query and avoid the OFFSET. You can continue to go forever with similar response time!<\/span><\/p>\n<pre class=\"theme:github whitespace-before:1 whitespace-after:1 lang:js decode:true\">\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"#operator\": \"IndexScan2\",\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"covers\": [\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"cover ((`travel-sample`.`type`))\",\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"cover ((`travel-sample`.`country`))\",\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"cover ((`travel-sample`.`city`))\",\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"cover ((meta(`travel-sample`).`id`))\",\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"cover ((meta(`travel-sample`).`id`))\"\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0],\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"index\": \"ixtypectcy\",\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"index_id\": \"5e43ce0471785942\",\r\n\u00a0\u00a0\u00a0\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"index_projection\": {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"entry_keys\": [\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a00,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a01,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a02\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0],\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"primary_key\": true\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0},\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"keyspace\": \"travel-sample\",\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"limit\": \"5\",\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"namespace\": \"default\",\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"spans\": [\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0{\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"exact\": true,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"range\": [\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0{\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"high\": \"\\\"hotel\\\"\",\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"inclusion\": 3,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"low\": \"\\\"hotel\\\"\"\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0},\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0{\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"inclusion\": 1,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"low\": \"\\\"France\\\"\"\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0},\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0{\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"inclusion\": 1,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"low\": \"\\\"Avignon\\\"\"\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0},\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0{\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"inclusion\": 0,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"low\": \"\\\"038c8a13-e1e7-4848-80ec-8819ff923602\\\"\"\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0]\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0],<\/pre>\n<p><span style=\"font-weight: 400\"><strong>SUMMARY:<\/strong> <\/span><\/p>\n<p><span style=\"font-weight: 400\">You just learned how pagination works. You also learned how the Couchbase N1QL optimizer exploits the index to improve the efficiency of query pagination performance. \u00a0You can use keyset pagination techniques to improve the performance of OFFSET further using the technique written.<\/span><b><\/b><\/p>\n<p><b>References:<\/b><\/p>\n<ol>\n<li><a href=\"https:\/\/www.couchbase.com\/blog\/optimizing-database-pagination-using-couchbase-n1ql\/\">https:\/\/www.couchbase.com\/blog\/optimizing-database-pagination-using-couchbase-n1ql\/<\/a><\/li>\n<li style=\"font-weight: 400\"><a href=\"https:\/\/www.slideshare.net\/MarkusWinand\/p2d2-pagination-done-the-postgresql-way?qid=237cb981-6e99-46e8-9197-96eacfa0bb86&amp;v=&amp;b=&amp;from_search=3\"><span style=\"font-weight: 400\">https:\/\/www.slideshare.net\/MarkusWinand\/p2d2-pagination-done-the-postgresql-way<\/span><\/a><\/li>\n<li style=\"font-weight: 400\"><a href=\"https:\/\/use-the-index-luke.com\/sql\/partial-results\/fetch-next-page\"><span style=\"font-weight: 400\">https:\/\/use-the-index-luke.com\/sql\/partial-results\/fetch-next-page<\/span><\/a><\/li>\n<li style=\"font-weight: 400\"><a href=\"https:\/\/blog.jooq.org\/2013\/10\/26\/faster-sql-paging-with-jooq-using-the-seek-method\/\"><span style=\"font-weight: 400\">https:\/\/blog.jooq.org\/2013\/10\/26\/faster-sql-paging-with-jooq-using-the-seek-method\/<\/span><\/a><\/li>\n<\/ol>\n<p><b>Appendix: <\/b><span style=\"font-weight: 400\">Optimize the query with order, offset, limit to exploit index ordering<\/span><\/p>\n<p><span style=\"font-weight: 400\">Query ORDER BY evaluation can exploit the Index order with the indexer returning results in the required order and the query does not have to modify the order (e.g. does grouping,join). Whenever the order of record changes we need to use sort data to satisfy the ORDER BY clause.<\/span><\/p>\n<p>&nbsp;<\/p>\n<ul>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">Index order will not be used when the from clause contains aggregates.<\/span><\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">Index order will not be used when GROUP BY clause present.<\/span><\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">Index order will not be used when JOINs, NEST, or UNNEST are used.<\/span><\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">Index order will not be used when DISTINCT is present.<\/span><\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">Index order will not be used when SET operators are used. This includes UNION, UNION ALL, INTERSECT, EXCEPT, EXCEPTALL.<\/span><\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">Index order will not be used when Primary Scan is involved.<\/span><\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">Index order will not be used when intersect or union scan is involved.<\/span><\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">Index order will not be used when Array Indexes are involved.<\/span><\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">Index order will not be used when order collation (ASC, DESC) doesn&#8217;t match with index collation.<\/span><\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">Index order will not be used when any order term is not matched with index keys.<\/span><\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">If Query exploits the Index order Order operator will not present in the EXPLAIN of the query.<\/span><\/li>\n<\/ul>\n<p><span style=\"font-weight: 400\">OFFSET and LIMIT can be pushed to IndexScan <\/span><\/p>\n<ul>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">when the ORDER BY is present and is able to exploit the index order<\/span><\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">When query ORDER BY is not present<\/span><\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">All the predicates are pushed to indexer as part of the spans<\/span><\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">Pushed spans values are exact <\/span><\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">Based on spans Indexer doesn&#8217;t generate false positives<\/span><\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">Query does not further reduce index scan results.<\/span><\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">If the OFFSET is pushed to indexer &#8220;offset&#8221; will appear in IndexScan section of the EXPLAIN and Offset Operator will not present at the end of the EXPLAIN.<\/span><\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">If the limit is pushed to indexer &#8220;limit&#8221; will appear in IndexScan section of the EXPLAIN. <\/span><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Read the pagination background in my previous article: https:\/\/www.couchbase.com\/blog\/optimizing-database-pagination-using-couchbase-n1ql\/ Pagination is the task of dividing the potential result into pages and retrieving the required pages, one by one on demand. \u00a0Using OFFSET and LIMIT is the easy way to write [&hellip;]<\/p>\n","protected":false},"author":55,"featured_media":4429,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"inline_featured_image":false,"footnotes":""},"categories":[1814,1815,9417,1812],"tags":[1998,1696,1261,2081],"ppma_author":[8929],"class_list":["post-4421","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-application-design","category-best-practices-and-tutorials","category-performance","category-n1ql-query","tag-application","tag-indexing","tag-json","tag-pagination"],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v27.3 (Yoast SEO v27.3) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>OFFSET and Keyset Pagination in N1QL Query | Couchbase<\/title>\n<meta name=\"description\" content=\"Pagination is a common application and has a major impact on customer experience. Here are detailed issues and solutions with Couchbase N1QL.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.couchbase.com\/blog\/ko\/offset-keyset-pagination-n1ql-query-couchbase\/\" \/>\n<meta property=\"og:locale\" content=\"ko_KR\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Database Pagination: Using OFFSET and Keyset in N1QL\" \/>\n<meta property=\"og:description\" content=\"Pagination is a common application and has a major impact on customer experience. Here are detailed issues and solutions with Couchbase N1QL.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.couchbase.com\/blog\/ko\/offset-keyset-pagination-n1ql-query-couchbase\/\" \/>\n<meta property=\"og:site_name\" content=\"The Couchbase Blog\" \/>\n<meta property=\"article:published_time\" content=\"2018-01-11T19:56:34+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-06-14T03:20:11+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2018\/01\/arrows2.png\" \/>\n\t<meta property=\"og:image:width\" content=\"853\" \/>\n\t<meta property=\"og:image:height\" content=\"363\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Keshav Murthy\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@rkeshavmurthy\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Keshav Murthy\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"11\ubd84\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/offset-keyset-pagination-n1ql-query-couchbase\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/offset-keyset-pagination-n1ql-query-couchbase\\\/\"},\"author\":{\"name\":\"Keshav Murthy\",\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/#\\\/schema\\\/person\\\/c261644262bf98e146372fe647682636\"},\"headline\":\"Database Pagination: Using OFFSET and Keyset in N1QL\",\"datePublished\":\"2018-01-11T19:56:34+00:00\",\"dateModified\":\"2025-06-14T03:20:11+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/offset-keyset-pagination-n1ql-query-couchbase\\\/\"},\"wordCount\":2258,\"commentCount\":1,\"publisher\":{\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/offset-keyset-pagination-n1ql-query-couchbase\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/wp-content\\\/uploads\\\/sites\\\/1\\\/2018\\\/01\\\/arrows2.png\",\"keywords\":[\"application\",\"Indexing\",\"JSON\",\"pagination\"],\"articleSection\":[\"Application Design\",\"Best Practices and Tutorials\",\"High Performance\",\"SQL++ \\\/ N1QL Query\"],\"inLanguage\":\"ko-KR\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/offset-keyset-pagination-n1ql-query-couchbase\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/offset-keyset-pagination-n1ql-query-couchbase\\\/\",\"url\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/offset-keyset-pagination-n1ql-query-couchbase\\\/\",\"name\":\"OFFSET and Keyset Pagination in N1QL Query | Couchbase\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/offset-keyset-pagination-n1ql-query-couchbase\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/offset-keyset-pagination-n1ql-query-couchbase\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/wp-content\\\/uploads\\\/sites\\\/1\\\/2018\\\/01\\\/arrows2.png\",\"datePublished\":\"2018-01-11T19:56:34+00:00\",\"dateModified\":\"2025-06-14T03:20:11+00:00\",\"description\":\"Pagination is a common application and has a major impact on customer experience. Here are detailed issues and solutions with Couchbase N1QL.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/offset-keyset-pagination-n1ql-query-couchbase\\\/#breadcrumb\"},\"inLanguage\":\"ko-KR\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/offset-keyset-pagination-n1ql-query-couchbase\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"ko-KR\",\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/offset-keyset-pagination-n1ql-query-couchbase\\\/#primaryimage\",\"url\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/wp-content\\\/uploads\\\/sites\\\/1\\\/2018\\\/01\\\/arrows2.png\",\"contentUrl\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/wp-content\\\/uploads\\\/sites\\\/1\\\/2018\\\/01\\\/arrows2.png\",\"width\":853,\"height\":363},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/offset-keyset-pagination-n1ql-query-couchbase\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Database Pagination: Using OFFSET and Keyset in N1QL\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/\",\"name\":\"The Couchbase Blog\",\"description\":\"Couchbase, the NoSQL Database\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"ko-KR\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/#organization\",\"name\":\"The Couchbase Blog\",\"url\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"ko-KR\",\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/wp-content\\\/uploads\\\/2023\\\/04\\\/admin-logo.png\",\"contentUrl\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/wp-content\\\/uploads\\\/2023\\\/04\\\/admin-logo.png\",\"width\":218,\"height\":34,\"caption\":\"The Couchbase Blog\"},\"image\":{\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/#\\\/schema\\\/logo\\\/image\\\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/#\\\/schema\\\/person\\\/c261644262bf98e146372fe647682636\",\"name\":\"Keshav Murthy\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"ko-KR\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/af74df754db27152971d0aed2f323ead5a1f9fe5afd0209af91e12e784451224?s=96&d=mm&r=g4e51d72fc07c662aa791316deafffac4\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/af74df754db27152971d0aed2f323ead5a1f9fe5afd0209af91e12e784451224?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/af74df754db27152971d0aed2f323ead5a1f9fe5afd0209af91e12e784451224?s=96&d=mm&r=g\",\"caption\":\"Keshav Murthy\"},\"description\":\"Keshav Murthy is a Vice President at Couchbase R&amp;D. Previously, he was at MapR, IBM, Informix, Sybase, with more than 20 years of experience in database design &amp; development. He lead the SQL and NoSQL R&amp;D team at IBM Informix. He has received two President's Club awards at Couchbase, two Outstanding Technical Achievement Awards at IBM. Keshav has a bachelor's degree in Computer Science and Engineering from the University of Mysore, India, and has received twenty four US patents.\",\"sameAs\":[\"https:\\\/\\\/blog.planetnosql.com\\\/\",\"https:\\\/\\\/x.com\\\/rkeshavmurthy\"],\"url\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/ko\\\/author\\\/keshav-murthy\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"OFFSET and Keyset Pagination in N1QL Query | Couchbase","description":"\ud398\uc774\uc9c0 \ub9e4\uae40\uc740 \uc77c\ubc18\uc801\uc778 \uc560\ud50c\ub9ac\ucf00\uc774\uc158\uc774\uba70 \uace0\uac1d \uacbd\ud5d8\uc5d0 \ud070 \uc601\ud5a5\uc744 \ubbf8\uce69\ub2c8\ub2e4. \ub2e4\uc74c\uc740 Couchbase N1QL\uc758 \uc790\uc138\ud55c \ubb38\uc81c\uc640 \ud574\uacb0 \ubc29\ubc95\uc785\ub2c8\ub2e4.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.couchbase.com\/blog\/ko\/offset-keyset-pagination-n1ql-query-couchbase\/","og_locale":"ko_KR","og_type":"article","og_title":"Database Pagination: Using OFFSET and Keyset in N1QL","og_description":"Pagination is a common application and has a major impact on customer experience. Here are detailed issues and solutions with Couchbase N1QL.","og_url":"https:\/\/www.couchbase.com\/blog\/ko\/offset-keyset-pagination-n1ql-query-couchbase\/","og_site_name":"The Couchbase Blog","article_published_time":"2018-01-11T19:56:34+00:00","article_modified_time":"2025-06-14T03:20:11+00:00","og_image":[{"width":853,"height":363,"url":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2018\/01\/arrows2.png","type":"image\/png"}],"author":"Keshav Murthy","twitter_card":"summary_large_image","twitter_creator":"@rkeshavmurthy","twitter_misc":{"Written by":"Keshav Murthy","Est. reading time":"11\ubd84"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.couchbase.com\/blog\/offset-keyset-pagination-n1ql-query-couchbase\/#article","isPartOf":{"@id":"https:\/\/www.couchbase.com\/blog\/offset-keyset-pagination-n1ql-query-couchbase\/"},"author":{"name":"Keshav Murthy","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/c261644262bf98e146372fe647682636"},"headline":"Database Pagination: Using OFFSET and Keyset in N1QL","datePublished":"2018-01-11T19:56:34+00:00","dateModified":"2025-06-14T03:20:11+00:00","mainEntityOfPage":{"@id":"https:\/\/www.couchbase.com\/blog\/offset-keyset-pagination-n1ql-query-couchbase\/"},"wordCount":2258,"commentCount":1,"publisher":{"@id":"https:\/\/www.couchbase.com\/blog\/#organization"},"image":{"@id":"https:\/\/www.couchbase.com\/blog\/offset-keyset-pagination-n1ql-query-couchbase\/#primaryimage"},"thumbnailUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2018\/01\/arrows2.png","keywords":["application","Indexing","JSON","pagination"],"articleSection":["Application Design","Best Practices and Tutorials","High Performance","SQL++ \/ N1QL Query"],"inLanguage":"ko-KR","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.couchbase.com\/blog\/offset-keyset-pagination-n1ql-query-couchbase\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.couchbase.com\/blog\/offset-keyset-pagination-n1ql-query-couchbase\/","url":"https:\/\/www.couchbase.com\/blog\/offset-keyset-pagination-n1ql-query-couchbase\/","name":"OFFSET and Keyset Pagination in N1QL Query | Couchbase","isPartOf":{"@id":"https:\/\/www.couchbase.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.couchbase.com\/blog\/offset-keyset-pagination-n1ql-query-couchbase\/#primaryimage"},"image":{"@id":"https:\/\/www.couchbase.com\/blog\/offset-keyset-pagination-n1ql-query-couchbase\/#primaryimage"},"thumbnailUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2018\/01\/arrows2.png","datePublished":"2018-01-11T19:56:34+00:00","dateModified":"2025-06-14T03:20:11+00:00","description":"\ud398\uc774\uc9c0 \ub9e4\uae40\uc740 \uc77c\ubc18\uc801\uc778 \uc560\ud50c\ub9ac\ucf00\uc774\uc158\uc774\uba70 \uace0\uac1d \uacbd\ud5d8\uc5d0 \ud070 \uc601\ud5a5\uc744 \ubbf8\uce69\ub2c8\ub2e4. \ub2e4\uc74c\uc740 Couchbase N1QL\uc758 \uc790\uc138\ud55c \ubb38\uc81c\uc640 \ud574\uacb0 \ubc29\ubc95\uc785\ub2c8\ub2e4.","breadcrumb":{"@id":"https:\/\/www.couchbase.com\/blog\/offset-keyset-pagination-n1ql-query-couchbase\/#breadcrumb"},"inLanguage":"ko-KR","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.couchbase.com\/blog\/offset-keyset-pagination-n1ql-query-couchbase\/"]}]},{"@type":"ImageObject","inLanguage":"ko-KR","@id":"https:\/\/www.couchbase.com\/blog\/offset-keyset-pagination-n1ql-query-couchbase\/#primaryimage","url":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2018\/01\/arrows2.png","contentUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2018\/01\/arrows2.png","width":853,"height":363},{"@type":"BreadcrumbList","@id":"https:\/\/www.couchbase.com\/blog\/offset-keyset-pagination-n1ql-query-couchbase\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.couchbase.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Database Pagination: Using OFFSET and Keyset in N1QL"}]},{"@type":"WebSite","@id":"https:\/\/www.couchbase.com\/blog\/#website","url":"https:\/\/www.couchbase.com\/blog\/","name":"\uce74\uc6b0\uce58\ubca0\uc774\uc2a4 \ube14\ub85c\uadf8","description":"NoSQL \ub370\uc774\ud130\ubca0\uc774\uc2a4, Couchbase","publisher":{"@id":"https:\/\/www.couchbase.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.couchbase.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"ko-KR"},{"@type":"Organization","@id":"https:\/\/www.couchbase.com\/blog\/#organization","name":"\uce74\uc6b0\uce58\ubca0\uc774\uc2a4 \ube14\ub85c\uadf8","url":"https:\/\/www.couchbase.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"ko-KR","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2023\/04\/admin-logo.png","contentUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2023\/04\/admin-logo.png","width":218,"height":34,"caption":"The Couchbase Blog"},"image":{"@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/c261644262bf98e146372fe647682636","name":"\ucf00\uc0e4\ube0c \uba38\uc2dc","image":{"@type":"ImageObject","inLanguage":"ko-KR","@id":"https:\/\/secure.gravatar.com\/avatar\/af74df754db27152971d0aed2f323ead5a1f9fe5afd0209af91e12e784451224?s=96&d=mm&r=g4e51d72fc07c662aa791316deafffac4","url":"https:\/\/secure.gravatar.com\/avatar\/af74df754db27152971d0aed2f323ead5a1f9fe5afd0209af91e12e784451224?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/af74df754db27152971d0aed2f323ead5a1f9fe5afd0209af91e12e784451224?s=96&d=mm&r=g","caption":"Keshav Murthy"},"description":"\ucf00\uc0e4\ube0c \uba38\uc2dc\ub294 Couchbase R&amp;D\uc758 \ubd80\uc0ac\uc7a5\uc785\ub2c8\ub2e4. \uc774\uc804\uc5d0\ub294 MapR, IBM, Informix, Sybase\uc5d0\uc11c \uadfc\ubb34\ud588\uc73c\uba70 \ub370\uc774\ud130\ubca0\uc774\uc2a4 \uc124\uacc4 \ubc0f \uac1c\ubc1c \ubd84\uc57c\uc5d0\uc11c 20\ub144 \uc774\uc0c1\uc758 \uacbd\ub825\uc744 \uc313\uc558\uc2b5\ub2c8\ub2e4. IBM Informix\uc5d0\uc11c SQL \ubc0f NoSQL R&amp;D \ud300\uc744 \uc774\ub04c\uc5c8\uc2b5\ub2c8\ub2e4. Couchbase\uc5d0\uc11c \ub450 \ubc88\uc758 President's Club \uc0c1\uc744, IBM\uc5d0\uc11c \ub450 \ubc88\uc758 \ub6f0\uc5b4\ub09c \uae30\uc220 \uc5c5\uc801\uc0c1\uc744 \uc218\uc0c1\ud588\uc2b5\ub2c8\ub2e4. \uc778\ub3c4 \ub9c8\uc774\uc18c\ub974 \ub300\ud559\uad50\uc5d0\uc11c \ucef4\ud4e8\ud130 \uacfc\ud559 \ubc0f \uacf5\ud559 \ud559\uc0ac \ud559\uc704\ub97c \ubc1b\uc558\uc73c\uba70, 24\uac1c\uc758 \ubbf8\uad6d \ud2b9\ud5c8\ub97c \ubcf4\uc720\ud558\uace0 \uc788\uc2b5\ub2c8\ub2e4.","sameAs":["https:\/\/blog.planetnosql.com\/","https:\/\/x.com\/rkeshavmurthy"],"url":"https:\/\/www.couchbase.com\/blog\/ko\/author\/keshav-murthy\/"}]}},"acf":[],"authors":[{"term_id":8929,"user_id":55,"is_guest":0,"slug":"keshav-murthy","display_name":"Keshav Murthy","avatar_url":"https:\/\/secure.gravatar.com\/avatar\/af74df754db27152971d0aed2f323ead5a1f9fe5afd0209af91e12e784451224?s=96&d=mm&r=g","0":null,"1":"","2":"","3":"","4":"","5":"","6":"","7":"","8":""}],"_links":{"self":[{"href":"https:\/\/www.couchbase.com\/blog\/ko\/wp-json\/wp\/v2\/posts\/4421","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.couchbase.com\/blog\/ko\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.couchbase.com\/blog\/ko\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/ko\/wp-json\/wp\/v2\/users\/55"}],"replies":[{"embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/ko\/wp-json\/wp\/v2\/comments?post=4421"}],"version-history":[{"count":0,"href":"https:\/\/www.couchbase.com\/blog\/ko\/wp-json\/wp\/v2\/posts\/4421\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/ko\/wp-json\/wp\/v2\/media\/4429"}],"wp:attachment":[{"href":"https:\/\/www.couchbase.com\/blog\/ko\/wp-json\/wp\/v2\/media?parent=4421"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/ko\/wp-json\/wp\/v2\/categories?post=4421"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/ko\/wp-json\/wp\/v2\/tags?post=4421"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/ko\/wp-json\/wp\/v2\/ppma_author?post=4421"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}