【问题标题】:How to get the cursor of each entity in a App Engine datastore query without performance hit?如何在不影响性能的情况下获取 App Engine 数据存储区查询中每个实体的光标?
【发布时间】:2018-12-30 09:24:32
【问题描述】:

我有一个使用游标(Objectify v5)的数据存储查询,我想在结果列表中的每个项目之后获取游标。代码如下所示:

public List<Puzzle> queryWithCursor(String cursor, String order, int limit) {
  Query<Puzzle> query = ObjectifyService.ofy()
    .load()
    .type(Puzzle.class)
    .order(order)
    .limit(limit);
  query = query.startAt(Cursor.fromWebSafeString(cursor));
  List<Puzzle> puzzles = new ArrayList<>();
  QueryResultIterator<Puzzle> iterator = query.iterator();
  while (iterator.hasNext()) {
    Puzzle puzzle = iterator.next();
    puzzle.setCursor(iterator.getCursor().toWebSafeString());
    puzzles.add(puzzle);
  }
  return puzzles;
}

虽然该方法正常工作,但它会在后台触发大量 Datastore 查询。基本上,每次 iterator.getCursor() 运行时,它都会触发一个额外的查询。我从 Stackdriver Trace 了解到,如果 limit 为 20,则该方法总共会触发 19 个查询(似乎最后一个 .getCursor() 不会触发额外的查询)。所以这种方法比使用偏移量的类似查询更慢,成本更高。

这真的是一个错误吗?有没有办法避免性能下降?

【问题讨论】:

  • 在每次迭代中获取光标似乎是一件不寻常的事情 - 这可能是一个不同的问题,询问您想要实现的目标,可以为您提供更多有用的答案
  • 嗨,格雷格,感谢您的回复。我认为您提供更多上下文是正确的。我有 2 个屏幕:PuzzleList 和 PuzzleDetail。 PuzzleList 显示一个谜题列表,当单击一个谜题时,它会打开 PuzzleDetail。从 PuzzleDetail 中,有一个 Next 按钮可以直接转到下一个未解决的谜题。这就是为什么我需要为 PuzzleList 中的每个拼图提供光标,以便我可以实现 Go Next 功能。目前我在查询中使用偏移量而不是游标,但由于读取操作的数量非常多,成本太高。
  • 我不是 100% 确定我理解了你的问题,但是,有什么理由让你相信它应该减少查询?根据this,它会从光标定义的位置恢复查询,因此这将是一个新的 API 调用,这就是它出现在 Stackdriver 上的原因。如果我误解了您的问题,请告诉我。
  • @Hieu 您可以将您使用的“订单”传递给详细信息页面,然后使用它来构造适当的查询:for 'next' - 'ORDER by $orderField WHERE $orderField > currentItem。 $orderField LIMIT 1'(和
  • 嗨格雷格,这对我不起作用,因为我想返回下一个“未解决”的难题,即我必须继续往下看,直到找到当前用户尚未解决的难题.

标签: performance google-app-engine objectify datastore database-cursor


【解决方案1】:

这实际上是数据存储的基本行为,至少在旧 sdk 中是这样(与 Objectify 6 使用的新 sdk 不同,可能相同,也可能不同)。在非批处理边界处调用 getCursor() 会重新启动查询。您可以使用低级 API 进行尝试。

有一个解决方法:创建自己的 Cursor 类。它应该由低级光标和偏移量组成。显式设置chunk() 大小,然后您的光标应包含索引0 处的Cursor 加上块中的偏移量。

然后,当您想在该光标处重新启动查询时,请使用 .cursor(batchStartCursor).offset(offsetIntoBatch)

【讨论】:

  • 太棒了,这完美地回答了我的问题。感谢您的解决方法。
【解决方案2】:
import com.google.appengine.api.datastore.Cursor;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.PreparedQuery;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Query.SortDirection;
import com.google.appengine.api.datastore.QueryResultList;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class ListPeopleServlet extends HttpServlet {

  static final int PAGE_SIZE = 15;
  private final DatastoreService datastore;

  public ListPeopleServlet() {
    datastore = DatastoreServiceFactory.getDatastoreService();
  }

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp)
      throws ServletException, IOException {
    FetchOptions fetchOptions = FetchOptions.Builder.withLimit(PAGE_SIZE);

    // If this servlet is passed a cursor parameter, let's use it.
    String startCursor = req.getParameter("cursor");
    if (startCursor != null) {
      fetchOptions.startCursor(Cursor.fromWebSafeString(startCursor));
    }

    Query q = new Query("Person").addSort("name", SortDirection.ASCENDING);
    PreparedQuery pq = datastore.prepare(q);

    QueryResultList<Entity> results;
    try {
      results = pq.asQueryResultList(fetchOptions);
    } catch (IllegalArgumentException e) {
      // IllegalArgumentException happens when an invalid cursor is used.
      // A user could have manually entered a bad cursor in the URL or there
      // may have been an internal implementation detail change in App Engine.
      // Redirect to the page without the cursor parameter to show something
      // rather than an error.
      resp.sendRedirect("/people");
      return;
    }

    resp.setContentType("text/html");
    resp.setCharacterEncoding("UTF-8");
    PrintWriter w = resp.getWriter();
    w.println("<!DOCTYPE html>");
    w.println("<meta charset=\"utf-8\">");
    w.println("<title>Cloud Datastore Cursor Sample</title>");
    w.println("<ul>");
    for (Entity entity : results) {
      w.println("<li>" + entity.getProperty("name") + "</li>");
    }
    w.println("</ul>");

    String cursorString = results.getCursor().toWebSafeString();

    // This servlet lives at '/people'.
    w.println("<a href='/people?cursor=" + cursorString + "'>Next page</a>");
  }
}

【讨论】:

  • 谢谢,但这并不能真正回答我的问题。我想要的是列表中每个项目之后的光标,而不仅仅是列表末尾的一个光标。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-11-04
  • 2010-12-03
  • 2011-02-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-02-11
相关资源
最近更新 更多