HRDを使った時の、検索結果のキャッシュについて

GAEで検索処理でのDatastore Reads Operationsを減らすにはMemcacheを使って検索結果をキャッシュするのが効果的だといろいろなサイトで見かけます。

例えばEngineerとSkillというModelがあるとします。

@Model(schemaVersion = 1)
public class Engineer implements Serializable {

    private static final long serialVersionUID = 1L;

    @Attribute(primaryKey = true)
    private Key key;
    
    private String name;

    @Attribute(version = true)
    private Long version;
    
    // setter,getter省略
}

@Model(schemaVersion = 1)
public class Skill implements Serializable {

    private static final long serialVersionUID = 1L;

    @Attribute(primaryKey = true)
    private Key key;

    private Key engineerKey;
    
    private String skillName;

    @Attribute(version = true)
    private Long version;
    
    // setter,getter省略
}

SkillServiceで登録/検索する処理を作成します。
データを登録したらMemcacheのキーを削除し、検索するときはMemcacheから取得、なければDatastoreから検索する形です。

// 登録/更新処理
public static Key put(Key engineerKey,String skillName)
{
	Skill skill = new Skill();
	skill.setEngineerKey(engineerKey);
	skill.setSkillName(skillName);
	Datastore.put(skill);
	// Memcache削除
	Memcache.delete(engineerKey);
}

// 検索処理
public static List<Skill> findList(Key engineerKey)
{
	List<Skill> skillList = Memcache.get(engineerKey);
	if(skillList != null)
	{
		return skillList;
	}
	
	skillList = Datastore.query(meta).filter(meta.engineerKey.equal(engineerKey)).asList();
	Memcache.put(engineerKey,skillList);
	
	return skillList;
}

これで問題なさそうな感じがしますが、HRDを使っているとQueryはEventual consistencyであり、
検索処理が実行されるタイミングでインデックスが反映されていない場合があるとのことです。
検索処理で結果をMemcacheに入れてしまうので、インデックスが反映されていない結果がMemcacheに入ってしまうと、
Memcacheが削除されるまで間違った値を保持し続ける形になってしまいます。

画面フローで言うと、

編集画面 → 更新処理 → 一覧画面

のようなケースで問題が発生する可能性がありそうです。

どのように対応すればいいのでしょうか。
いろいろなサイトを参考にさせていただいて、自分なりの考えをまとめてみました。
どんなパターンでも使える対策はないと思うので、状況に応じて適切に使い分ける必要がありそうです。

  • 画面フローの更新処理と一覧画面の間に「更新完了画面」をはさむ
    • インデックスの更新はほとんど数十ミリ秒、長くて2秒程度とのことなので、一覧画面の前に画面をはさんで時間稼ぎする方法です。シンプルですが、複数のユーザーが高い頻度でアクセスする情報をMemcacheに保存する場合などは使えないかもしれません。
  • put処理の後でMemcacheを削除するのではなく、Datastoreにputしたエンティティの情報をマージする
    • Memcacheの値とDatastoreのクエリー結果が同じになるように(ソート順とかも含めて)しておかなくてはいけませんが、検索結果の信頼性は高そうです。
  • Ancestor queryを使う
    • Engineerを親エンティティ、Skillを子エンティティとしたエンティティグループを構成してAncestor queryを使った場合は即時に正しい結果を取得できるとのことで、この値をMemcacheに保存しておけばよさそうです。
// Ancestor query
List<Skill> skillList = Datastore.query(Skill.class,engineerKey).asList();
  • KeyからGetする
  • Memcacheを使うのをやめる
    • ひとつの選択肢かなと思います。


参考にさせていただいたサイト
M/SからHRDへの変更した場合、設計は変える必要があるのか? - SiNBLOG
najeira: Eventual consistencyなクエリ結果のキャッシュ
https://developers.google.com/appengine/docs/java/datastore/hr/?hl=ja