2010年4月30日金曜日

データストアのメンテナンスモードに対応する

Google App Engineのデータストアは定期的にメンテナンスモードに入ります。 メンテナンス中はデータストアが読み取り専用になって一切の書き込みが禁止されます。

Python版にはCapabilityServiceというものが用意されていてこれらを調べるのは簡単ですが、Java版にはまだ用意されていないようです。

Java版で(無理せず)これをテストするには、次のようなサーブレットフィルタを用意してやります。

package com.example;

import java.io.IOException;
import java.util.concurrent.Future;

import javax.servlet.*;

import com.google.apphosting.api.ApiProxy;
import com.google.apphosting.api.ApiProxy.*;

public class MaintenanceFilter implements Filter {

    @Override
    public void init(FilterConfig conf) throws ServletException {
        return;
    }

    @Override
    public void destroy() {
        return;
    }

    @Override
    public void doFilter(
            ServletRequest req,
            ServletResponse res,
            FilterChain chain) throws IOException, ServletException {
        @SuppressWarnings("unchecked")
        Delegate<Environment> delegate = ApiProxy.getDelegate();
        try {
            ApiProxy.setDelegate(new MaintenanceDelegate(delegate));
            chain.doFilter(req, res);
        }
        finally {
            ApiProxy.setDelegate(delegate);
        }
    }

    private static class MaintenanceDelegate implements Delegate<Environment> {

        private final Delegate<Environment> delegate;

        MaintenanceDelegate(Delegate<Environment> delegate) {
            this.delegate = delegate;
        }

        @Override
        public void log(Environment env, LogRecord record) {
            this.delegate.log(env, record);
        }

        @Override
        public Future<byte[]> makeAsyncCall(
                Environment env,
                String service,
                String method,
                byte[] bytes,
                ApiConfig config) {
            return this.delegate.makeAsyncCall(env, service, method, bytes, config);
        }

        @Override
        public byte[] makeSyncCall(
                Environment env,
                String service,
                String method,
                byte[] bytes) throws ApiProxyException {
            if (service.equals("datastore_v3")) {
                if (method.equals("Put") || method.equals("Delete")) {
                    throw new CapabilityDisabledException(service, method);
                }
            }
            return this.delegate.makeSyncCall(env, service, method, bytes);
        }
    }
}

これをweb.xmlに登録します。このとき、「/_ah/」以下をフィルタするといろいろ不具合が発生するので、必要最小限にしましょう。

<filter>
    <filter-name>MaintenanceFilter</filter-name>
    <filter-class>com.gluegent.goose.soc.front.util.MaintenanceFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>MaintenanceFilter</filter-name>
    <url-pattern>/gadget/*</url-pattern>
    <dispatcher>REQUEST</dispatcher>
</filter-mapping>

このフィルタを経由した状態でデータストアに書き込みを行うと、CapabilityDisabledExceptionがスローされるようになります。 例外をキャッチしてメンテナンス中の処理を書いておきましょう。

try {
    // データストアへの書き込み
}
catch (CapabilityDisabledException e) {
    // メンテナンス中の処理
}

メンテナンスモードのときの動作がエミュレーションされるので、この状態で様々なテストを実施します。 なお、このフィルタを残したままサービスインするとひどいことになります。

追記

ご指摘いただきました。 メンテナンスモードのときはDatastoreService.allocateIds()やMemcacheなども動かなくなるので、makeSyncCallメソッドの中でもう少しトラップが必要です。 具体的にはこんな感じ:

@Override
public byte[] makeSyncCall(
        Environment env,
        String service,
        String method,
        byte[] bytes) throws ApiProxyException {
    if (service.equals("datastore_v3")) {
        if (method.equals("Put") ||
                method.equals("Delete") ||
                method.equals("AllocateIds")) {
            throw new CapabilityDisabledException(service, method);
        }
    }
    else if (service.equals("memcache")) {
        throw new CapabilityDisabledException(service, method);
    }
    return this.delegate.makeSyncCall(env, service, method, bytes);
}

0 件のコメント:

コメントを投稿