Async Datastore API를 사용하면 Datastore를 병렬 비차단 방식으로 호출한 후 요청을 처리하는 과정에서 이후 시점에 호출 결과를 검색할 수 있습니다. 이 문서에서는 Async Datastore API의 다음과 같은 측면에 대해 설명합니다.
비동기 데이터 저장소 서비스로 작업
Async Datastore API에서는 AsyncDatastoreService 인터페이스의 메소드를 사용하여 데이터 저장소를 호출합니다. DatastoreServiceFactory 클래스의 getAsyncDatastoreService() 클래스 메서드를 호출하여 이 객체를 가져올 수 있습니다.
import com.google.appengine.api.datastore.AsyncDatastoreService; import com.google.appengine.api.datastore.DatastoreServiceFactory; // ... AsyncDatastoreService datastore = DatastoreServiceFactory.getAsyncDatastoreService();
AsyncDatastoreService
는 DatastoreService와 같은 작업을 지원하지만 대부분의 메서드가 이후 시점에 결과가 차단될 수 있는 Future를 즉시 반환한다는 점이 다릅니다. 예를 들어 DatastoreService.get()은 Entity를 반환하지만 AsyncDatastoreService.get()은 Future<Entity>
를 반환합니다.
// ... Key key = KeyFactory.createKey("Employee", "Max"); // Async call returns immediately Future<Entity> entityFuture = datastore.get(key); // Do other stuff while the get operation runs in the background... // Blocks if the get operation has not finished, otherwise returns instantly Entity entity = entityFuture.get();
참고: get() 메서드를 호출할 때까지 예외가 발생하지 않습니다. 이 메서드를 호출하면 비동기 작업의 성공 여부를 확인할 수 있습니다.
AsyncDatastoreService
를 사용하지만 작업을 동기식으로 실행해야 하는 경우 해당 AsyncDatastoreService
메서드를 호출한 후 즉시 결과를 차단합니다.
// ... Entity entity = new Employee("Employee", "Alfred"); // ... populate entity properties // Make a sync call via the async interface Key key = datastore.put(key).get();
비동기 트랜잭션으로 작업
Async Datastore API 호출은 동기식 호출과 마찬가지로 트랜잭션에 참여할 수 있습니다. 다음은 단일 트랜잭션 내에서 Employee
의 급여를 조정하고 Employee
와 동일한 항목 그룹에 SalaryAdjustment
항목을 추가로 작성하는 함수입니다.
void giveRaise(AsyncDatastoreService datastore, Key employeeKey, long raiseAmount) throws Exception { Future<Transaction> txn = datastore.beginTransaction(); // Async call to lookup the Employee entity Future<Entity> employeeEntityFuture = datastore.get(employeeKey); // Create and put a SalaryAdjustment entity in parallel with the lookup Entity adjustmentEntity = new Entity("SalaryAdjustment", employeeKey); adjustmentEntity.setProperty("adjustment", raiseAmount); adjustmentEntity.setProperty("adjustmentDate", new Date()); datastore.put(adjustmentEntity); // Fetch the result of our lookup to make the salary adjustment Entity employeeEntity = employeeEntityFuture.get(); long salary = (Long) employeeEntity.getProperty("salary"); employeeEntity.setProperty("salary", salary + raiseAmount); // Re-put the Employee entity with the adjusted salary. datastore.put(employeeEntity); txn.get().commit(); // could also call txn.get().commitAsync() here }
이 샘플에서는 트랜잭션 없는 비동기 호출과 트랜잭션 있는 비동기 호출 사이의 중요한 차이점을 보여줍니다. 트랜잭션을 사용하지 않는 경우 개별 비동기 호출이 완료되었는지 확인하는 유일한 방법은 호출 시점에 반환된 Future
의 반환값을 가져오는 것입니다. 트랜잭션을 사용하는 경우 Transaction.commit()을 호출하면 커밋 전에 트랜잭션이 시작되었으므로 모든 비동기 호출의 결과를 차단합니다.
따라서 위의 예시에서는 commit()
을 호출할 때 SalaryAdjustment
항목을 삽입하는 비동기 호출이 여전히 미해결 상태일 수 있어도 삽입이 완료될 때까지 커밋이 수행되지 않습니다. 마찬가지로 commit()
대신 commitAsync()
를 호출하도록 선택하면 commitAsync()
에서 반환된 Future
에 대해 get()
을 호출하면 모든 미해결 비동기 호출이 완료될 때까지 차단됩니다.
참고: 트랜잭션에는 DatastoreService
또는 AsyncDatastoreService
의 특정 인스턴스가 아닌 특정 스레드가 연결됩니다. 즉, DatastoreService
로 트랜잭션을 시작하고 AsyncDatastoreService
로 비동기 호출을 수행하면 비동기 호출이 트랜잭션에 참여합니다. 또는 간단히 말해서 DatastoreService.getCurrentTransaction()
및 AsyncDatastoreService.getCurrentTransaction()
은 항상 동일한 Transaction
을 반환합니다.
Future 작업
Future Javadoc에서는 Async Datastore API가 반환하는 Future
를 적절히 활용하기 위해 알아야 할 대부분의 정보를 설명하지만 다음과 같이 App Engine에만 해당하는 몇 가지 사항도 숙지해야 합니다.
- Future.get(long timeout, TimeUnit unit)을 호출할 때 제한 시간은
AsyncDatastoreService
생성 시 설정되는 RPC 기한과 구분됩니다. 자세한 내용은 Cloud Datastore 쿼리의 데이터 일관성을 참조하세요. - Future.cancel(boolean mayInterruptIfRunning)을 호출할 때 해당 호출이
true
를 반환한다고 해서 Datastore의 상태가 변경되지 않는 것은 아닙니다. 즉,Future
취소는 트랜잭션 롤백과 동일하지 않습니다.
비동기 쿼리
명시적 비동기 API는 현재 쿼리용으로 노출되지 않습니다. 그러나 PreparedQuery.asIterable(), PreparedQuery.asIterator() 또는 PreparedQuery.asList(FetchOptions fetchOptions)를 호출하면 DatastoreService와 AsyncDatastoreService 모두 즉시 반환되며 비동기식으로 결과를 미리 가져옵니다. 따라서 쿼리 결과를 가져오는 동안 애플리케이션에서 병렬로 작업을 수행할 수 있습니다.
// ... Query q1 = new Query("Salesperson"); q1.setFilter(new FilterPredicate("dateOfHire", FilterOperator.LESS_THAN, oneMonthAgo)); // Returns instantly, query is executing in the background. Iterable<Entity> recentHires = datastore.prepare(q1).asIterable(); Query q2 = new Query("Customer"); q2.setFilter(new FilterPredicate("lastContact", FilterOperator.GREATER_THAN, oneYearAgo)); // Also returns instantly, query is executing in the background. Iterable<Entity> needsFollowup = datastore.prepare(q2).asIterable(); schedulePhoneCall(recentHires, needsFollowUp);
비동기 데이터 저장소 호출을 사용해야 하는 경우
DatastoreService 인터페이스가 노출하는 작업은 동기식입니다. 예를 들어 DatastoreService.get()
을 호출하면 Datastore 호출이 완료될 때까지 코드가 차단됩니다. 애플리케이션이 HTML에서 get()
의 결과를 렌더링하기만 하면 되는 경우 호출이 완료될 때까지 차단할 수 있습니다. 그러나 애플리케이션이 응답을 렌더링하기 위해 get()
과 Query
결과가 필요하고 get()
및 Query
에 데이터 종속 항목이 없으면 get()
이 완료될 때까지 기다렸다가 Query
를 시작하는 것은 시간 낭비입니다. 다음은 비동기 API를 사용하여 개선할 수 있는 코드의 예입니다.
DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); Key empKey = KeyFactory.createKey("Employee", "Max"); // Read employee data from the Datastore Entity employee = datastore.get(empKey); // Blocking for no good reason! // Fetch payment history Query query = new Query("PaymentHistory"); PreparedQuery pq = datastore.prepare(query); List<Entity> result = pq.asList(FetchOptions.Builder.withLimit(10)); renderHtml(employee, result);
get()
이 완료되기를 기다리는 대신 AsyncDatastoreService의 인스턴스를 사용하여 다음과 같이 비동기식으로 호출을 실행합니다.
AsyncDatastoreService datastore = DatastoreServiceFactory.getAsyncDatastoreService(); Key empKey = KeyFactory.createKey("Employee", "Max"); // Read employee data from the Datastore Future<Entity> employeeFuture = datastore.get(empKey); // Returns immediately! // Fetch payment history for the employee Query query = new Query("PaymentHistory", empKey); PreparedQuery pq = datastore.prepare(query); // Run the query while the employee is being fetched List<Entity> result = pq.asList(FetchOptions.Builder.withLimit(10)); // Implicitly performs query asynchronously Entity employee = employeeFuture.get(); // Blocking! renderHtml(employee, result);
이 코드의 동기식 버전과 비동기식 버전은 같은 양의 작업을 수행하므로 CPU 사용량이 비슷합니다. 그러나 비동기 버전에서는 두 Datastore 작업이 병렬로 실행되므로 지연 시간이 더 짧습니다. 일반적으로, 데이터 종속 항목이 없는 여러 Datastore 작업을 수행해야 하는 경우 AsyncDatastoreService
로 지연 시간을 크게 개선할 수 있습니다.