Go 1.11은 지원이 종료되었으며 2026년 1월 31일에 지원 중단됩니다. 지원 중단 후에는 조직에서 이전에 조직 정책을 사용하여 레거시 런타임의 배포를 다시 사용 설정한 경우에도 Go 1.11 애플리케이션을 배포할 수 없습니다. 기존 Go 1.11 애플리케이션은 지원 중단 날짜 이후에도 계속 실행되고 트래픽을 수신합니다. 지원되는 최신 Go 버전으로 마이그레이션하는 것이 좋습니다.
쿼리 커서는 애플리케이션이 쿼리 결과를 편리하게 일괄적으로 검색할 수 있게 해주므로 정수 오프셋으로 페이지를 나누는 방식보다 권장됩니다.
앱의 쿼리 구조화에 대한 자세한 내용은 쿼리를 참조하세요.
쿼리 커서
쿼리 커서를 사용하면 쿼리 오프셋의 오버헤드 발생 없이 애플리케이션에서 편리하게 일괄적으로 쿼리 결과를 검색할 수 있습니다. 검색 작업을 수행한 후 애플리케이션은 커서를 가져올 수 있습니다. 커서는 마지막으로 검색된 결과의 색인 위치를 표시하는 불투명한 base64 인코딩 문자열입니다. 애플리케이션은 이 문자열을 Datastore, Memcache, 태스크 큐 태스크 페이로드 등에 저장하거나 웹페이지에 HTTP GET 또는 POST 파라미터로 포함한 후 해당 커서를 후속 검색 작업의 시작점으로 사용하여 이전 검색이 끝난 지점부터 결과의 다음 배치를 가져올 수 있습니다. 검색 시 끝 커서를 지정하여 결과 집합이 반환되는 범위를 제한할 수도 있습니다.
오프셋과 커서 비교
Datastore가 정수 오프셋을 지원하지만 사용하지 마세요. 대신 커서를 사용하세요. 오프셋을 사용하면 건너뛴 항목이 애플리케이션에 반환되지는 않지만 내부적으로는 계속 검색됩니다. 건너뛴 항목은 쿼리의 지연 시간에 영향을 주며, 애플리케이션이 검색하는 데 소요된 읽기 작업에 대해 비용이 청구됩니다. 오프셋 대신 커서를 사용하면 이러한 모든 비용이 청구되지 않습니다.
쿼리 커서 예시
Go에서 애플리케이션은 Iterator 값의 Cursor 메서드를 호출하여 쿼리 결과를 검색한 후 커서를 가져옵니다. 애플리케이션은 커서 지점부터 추가 결과를 검색하기 위해 동일한 항목 종류, 필터, 정렬 순서를 사용하여 유사한 쿼리를 준비하고 쿼리의 Start 메서드에 커서를 전달한 후에 검색을 수행합니다.
// Create a query for all Person entities.q:=datastore.NewQuery("Person")// If the application stored a cursor during a previous request, use it.item,err:=memcache.Get(ctx,"person_cursor")iferr==nil{cursor,err:=datastore.DecodeCursor(string(item.Value))iferr==nil{q=q.Start(cursor)}}// Iterate over the results.t:=q.Run(ctx)for{varpPerson_,err:=t.Next(&p)iferr==datastore.Done{break}iferr!=nil{log.Errorf(ctx,"fetching next Person: %v",err)break}// Do something with the Person p}// Get updated cursor and store it for next time.ifcursor,err:=t.Cursor();err==nil{memcache.Set(ctx,&memcache.Item{Key:"person_cursor",Value:[]byte(cursor.String()),})}
커서 제한사항
다음과 같은 제한사항이 커서에 적용됩니다.
원본 쿼리를 수행한 동일한 애플리케이션에서만 그리고 같은 쿼리를 계속 진행할 경우에만 커서를 사용할 수 있습니다. 후속 검색 작업에서 커서를 사용하려면 동일한 항목 종류, 상위 필터, 속성 필터, 정렬 순서를 포함하여 원본 쿼리를 정확하게 다시 구성해야 합니다. 원래 생성된 쿼리와 동일하게 쿼리를 설정하지 않으면 커서를 사용하여 결과를 검색할 수 없습니다.
커서는 값이 여러 개인 속성에 불일치 필터나 정렬 순서를 사용하는 쿼리에서 예상대로 작동하지 않을 수도 있습니다. 이러한 다중 값 속성에 대한 중복 제거 로직은 다음 검색까지 지속되지 않으므로 동일한 결과가 한 번 이상 반환될 수 있습니다.
새로운 App Engine 출시 버전에서 내부 구현의 세부정보가 변경되면 여기에 종속된 커서가 무효화될 수 있습니다. 애플리케이션에서 이제는 유효하지 않은 커서를 사용하려고 시도하면 Datastore에서 오류를 반환합니다.
커서 및 데이터 업데이트
커서 위치는 마지막 결과가 반환된 후 결과 목록의 위치로 정의됩니다. 커서는 목록의 상대 위치가 아닙니다(오프셋이 아님). 결과에 대한 색인 스캔을 시작할 때 Datastore가 이동할 수 있는 위치를 표시하는 마커입니다. 커서를 사용하는 시점 간에 쿼리 결과가 변경되면 쿼리는 커서보다 뒤에 있는 결과에서 발생한 변경 사항만 알립니다. 쿼리의 커서 위치보다 앞에서 나타난 새 결과는 커서보다 뒤에 있는 결과를 가져오면 반환되지 않습니다.
마찬가지로, 항목이 더 이상 쿼리의 결과가 아니지만 커서보다 앞에 나타난 경우 커서보다 뒤에 나타나는 결과는 바뀌지 않습니다. 마지막으로 반환된 결과가 결과 집합에서 삭제된 경우, 커서는 다음 결과를 계속 찾을 수 있습니다.
쿼리 결과를 검색할 때 시작 커서와 끝 커서를 사용하여 Datastore에서 연속적인 결과 그룹을 반환할 수 있습니다. 시작 커서와 끝 커서를 사용하여 결과 검색 시 결과의 크기가 커서 생성 시점과 동일하다는 보장은 없습니다.
커서가 생성된 후 쿼리에서 사용되기 전에 Datastore에서 항목이 추가되거나 삭제될 수 있습니다.
[[["이해하기 쉬움","easyToUnderstand","thumb-up"],["문제가 해결됨","solvedMyProblem","thumb-up"],["기타","otherUp","thumb-up"]],[["이해하기 어려움","hardToUnderstand","thumb-down"],["잘못된 정보 또는 샘플 코드","incorrectInformationOrSampleCode","thumb-down"],["필요한 정보/샘플이 없음","missingTheInformationSamplesINeed","thumb-down"],["번역 문제","translationIssue","thumb-down"],["기타","otherDown","thumb-down"]],["최종 업데이트: 2025-09-04(UTC)"],[[["\u003cp\u003eThis API is designed for first-generation runtimes and is crucial when upgrading to corresponding second-generation runtimes, with a migration guide provided for App Engine Go 1.12+ users.\u003c/p\u003e\n"],["\u003cp\u003eQuery cursors enable applications to retrieve query results in batches, providing a more efficient method of pagination compared to using integer offsets, as they avoid retrieving skipped entities internally.\u003c/p\u003e\n"],["\u003cp\u003eAfter retrieving results, a cursor, which is an encoded string, can be obtained to mark the last result's position, allowing the application to save it and use it as a starting point for the next retrieval operation.\u003c/p\u003e\n"],["\u003cp\u003eCursors are limited to use by the originating application and require an exact replication of the initial query's conditions to be reused, while also being susceptible to invalidation from new App Engine releases.\u003c/p\u003e\n"],["\u003cp\u003eUsing cursors for pagination is preferred over offsets because it allows you to avoid costs and latency associated with retrieving entities internally that will just be skipped, in addition to being able to specify an end cursor to limit results.\u003c/p\u003e\n"]]],[],null,["# Query Cursors\n\n| This API is supported for first-generation runtimes and can be used when [upgrading to corresponding second-generation runtimes](/appengine/docs/standard/\n| go\n| /services/access). If you are updating to the App Engine Go 1.12+ runtime, refer to the [migration guide](/appengine/migration-center/standard/migrate-to-second-gen/go-differences) to learn about your migration options for legacy bundled services.\n\n*Query cursors* allow an application to retrieve a query's results in convenient\nbatches, and are recommended over using integer offsets for pagination.\nSee [Queries](/appengine/docs/legacy/standard/go111/datastore/queries) for more information on structuring queries for your app.\n\nQuery cursors\n-------------\n\n*Query cursors* allow an application to retrieve a query's results in convenient\nbatches without incurring the overhead of a query offset. After performing a\n[retrieval operation](/appengine/docs/legacy/standard/go111/datastore/retrieving-query-results), the application can obtain a\ncursor, which is an opaque base64-encoded string marking the index position of\nthe last result retrieved. The application can save this string, for example in\nDatastore, in Memcache, in a Task Queue task payload, or embedded in\na web page as an HTTP `GET` or `POST` parameter, and can then use the cursor as\nthe starting point for a subsequent retrieval operation to obtain the next batch\nof results from the point where the previous retrieval ended. A retrieval can\nalso specify an end cursor, to limit the extent of the result set returned.\n\nOffsets versus cursors\n----------------------\n\nAlthough Datastore supports integer offsets, you should avoid\nusing them. Instead, use cursors. Using an offset only avoids returning the\nskipped entities to your application, but these entities are still retrieved\ninternally. The skipped entities do affect the latency of the query, and your\napplication is billed for the read operations required to retrieve them. Using\ncursors instead of offsets lets you avoid all these costs.\n\nQuery cursor example\n--------------------\n\nIn Go, an application obtains a cursor after retrieving query results by calling\nthe `Iterator` value's [`Cursor`](/appengine/docs/legacy/standard/go111/datastore/reference#Iterator.Cursor) method. To retrieve\nadditional results from the point of the cursor, the application prepares a\nsimilar query with the same entity kind, filters, and\nsort orders, and passes the cursor to the query's\n[`Start`](/appengine/docs/legacy/standard/go111/datastore/reference#Query.Start) method before performing the retrieval: \n\n // Create a query for all Person entities.\n q := datastore.NewQuery(\"Person\")\n\n // If the application stored a cursor during a previous request, use it.\n item, err := memcache.Get(ctx, \"person_cursor\")\n if err == nil {\n \tcursor, err := datastore.DecodeCursor(string(item.Value))\n \tif err == nil {\n \t\tq = q.Start(cursor)\n \t}\n }\n\n // Iterate over the results.\n t := q.Run(ctx)\n for {\n \tvar p Person\n \t_, err := t.Next(&p)\n \tif err == datastore.Done {\n \t\tbreak\n \t}\n \tif err != nil {\n \t\tlog.Errorf(ctx, \"fetching next Person: %v\", err)\n \t\tbreak\n \t}\n \t// Do something with the Person p\n }\n\n // Get updated cursor and store it for next time.\n if cursor, err := t.Cursor(); err == nil {\n \tmemcache.Set(ctx, &memcache.Item{\n \t\tKey: \"person_cursor\",\n \t\tValue: []byte(cursor.String()),\n \t})\n }\n\n| **Caution:** Be careful when passing a Datastore cursor to a client, such as in a web form. Although the client cannot change the cursor value to access results outside of the original query, it is possible for it to decode the cursor to expose information about result entities, such as the application ID, entity kind, key name or numeric ID, ancestor keys, and properties used in the query's filters and sort orders. If you don't want users to have access to that information, you can encrypt the cursor, or store it and provide the user with an opaque key.\n\n### Limitations of cursors\n\nCursors are subject to the following limitations:\n\n- A cursor can be used only by the same application that performed the original query, and only to continue the same query. To use the cursor in a subsequent retrieval operation, you must reconstitute the original query exactly, including the same entity kind, ancestor filter, property filters, and sort orders. It is not possible to retrieve results using a cursor without setting up the same query from which it was originally generated.\n- Cursors don't always work as expected with a query that uses an inequality filter or a sort order on a property with multiple values. The de-duplication logic for such multiple-valued properties does not persist between retrievals, possibly causing the same result to be returned more than once.\n- New App Engine releases might change internal implementation details, invalidating cursors that depend on them. If an application attempts to use a cursor that is no longer valid, Datastore returns an error.\n\n### Cursors and data updates\n\nThe cursor's position is defined as the location in the result list after the\nlast result returned. A cursor is not a relative position in the list\n(it's not an offset); it's a marker to which Datastore can jump\nwhen starting an index scan for results. If the results for a query change\nbetween uses of a cursor, the query notices only changes that occur in results\nafter the cursor. If a new result appears before the cursor's position for the\nquery, it will not be returned when the results after the cursor are fetched.\nSimilarly, if an entity is no longer a result for a query but had appeared\nbefore the cursor, the results that appear after the cursor do not change. If\nthe last result returned is removed from the result set, the cursor still knows\nhow to locate the next result.\n\nWhen retrieving query results, you can use both a start cursor and an end cursor\nto return a continuous group of results from Datastore. When\nusing a start and end cursor to retrieve the results, you are not guaranteed\nthat the size of the results will be the same as when you generated the cursors.\nEntities may be added or deleted from Datastore between the\ntime the cursors are generated and when they are used in a query.\n\nWhat's next?\n------------\n\n- [Learn how to specify what a query returns and further control query\n results](/appengine/docs/legacy/standard/go111/datastore/retrieving-query-results).\n- Learn the [common restrictions](/appengine/docs/legacy/standard/go111/datastore/query-restrictions) for queries on Datastore.\n- [Understand data consistency](/appengine/docs/legacy/standard/go111/datastore/data-consistency) and how data consistency works with different types of queries on Datastore.\n- Learn the [basic syntax and structure of queries](/appengine/docs/legacy/standard/go111/datastore/queries) for Datastore."]]