Android API ক্লায়েন্ট-সাইড ক্যাশিং নির্দেশিকা

অ্যান্ড্রয়েড এপিআই কলে সাধারণত প্রতি আহ্বানে উল্লেখযোগ্য লেটেন্সি এবং গণনা জড়িত থাকে। ক্লায়েন্ট-সাইড ক্যাশিং তাই সহায়ক, সঠিক এবং পারফরম্যান্সকারী API ডিজাইন করার ক্ষেত্রে একটি গুরুত্বপূর্ণ বিবেচনা।

প্রেরণা

Android SDK-এ অ্যাপ ডেভেলপারদের কাছে প্রকাশ করা APIগুলি প্রায়শই Android ফ্রেমওয়ার্কের ক্লায়েন্ট কোড হিসাবে প্রয়োগ করা হয় যা একটি প্ল্যাটফর্ম প্রক্রিয়ায় একটি সিস্টেম পরিষেবাতে বাইন্ডার আইপিসি কল করে, যার কাজ হল কিছু গণনা করা এবং ক্লায়েন্টকে ফলাফল ফিরিয়ে দেওয়া। এই অপারেশনের লেটেন্সি সাধারণত তিনটি কারণের দ্বারা প্রভাবিত হয়:

  • IPC ওভারহেড: একটি মৌলিক আইপিসি কল সাধারণত একটি প্রাথমিক ইন-প্রসেস মেথড কলের লেটেন্সি 10,000x হয়।
  • সার্ভার-সাইড বিরোধ: ক্লায়েন্টের অনুরোধের প্রতিক্রিয়া হিসাবে সিস্টেম পরিষেবাতে করা কাজ অবিলম্বে শুরু নাও হতে পারে, উদাহরণস্বরূপ যদি একটি সার্ভার থ্রেড আগে আসা অন্যান্য অনুরোধগুলি পরিচালনা করতে ব্যস্ত থাকে।
  • সার্ভার-সাইড কম্পিউটেশন: সার্ভারে রিকোয়েস্ট হ্যান্ডেল করার জন্য কাজের জন্য গুরুত্বপূর্ণ কাজের প্রয়োজন হতে পারে।

আপনি ক্লায়েন্ট সাইডে একটি ক্যাশে প্রয়োগ করে এই তিনটি লেটেন্সি ফ্যাক্টরগুলিকে বাদ দিতে পারেন, যদি ক্যাশে থাকে:

  • সঠিক: ক্লায়েন্ট-সাইড ক্যাশে কখনই এমন ফলাফল ফেরত দেয় না যা সার্ভারটি যা ফেরত দেবে তার থেকে আলাদা হবে।
  • কার্যকরী: ক্লায়েন্টের অনুরোধগুলি প্রায়ই ক্যাশে থেকে পরিবেশন করা হয়, উদাহরণস্বরূপ ক্যাশে উচ্চ হিট রেট রয়েছে৷
  • দক্ষ: ক্লায়েন্ট-সাইড ক্যাশে ক্লায়েন্ট-সাইড রিসোর্সগুলির দক্ষ ব্যবহার করে, যেমন একটি কম্প্যাক্ট উপায়ে ক্যাশে করা ডেটা উপস্থাপন করে এবং ক্লায়েন্টের মেমরিতে খুব বেশি ক্যাশে ফলাফল বা বাসি ডেটা সংরক্ষণ না করে।

ক্লায়েন্ট সার্ভার ফলাফল ক্যাশিং বিবেচনা করুন

যদি ক্লায়েন্টরা প্রায়শই একই অনুরোধ একাধিকবার করে, এবং প্রত্যাবর্তিত মান সময়ের সাথে পরিবর্তিত না হয়, তাহলে আপনাকে অনুরোধের পরামিতি দ্বারা চাবি করা ক্লায়েন্ট লাইব্রেরিতে একটি ক্যাশে প্রয়োগ করা উচিত।

আপনার বাস্তবায়নে IpcDataCache ব্যবহার করার কথা বিবেচনা করুন:

public class BirthdayManager {
    private final IpcDataCache.QueryHandler<User, Birthday> mBirthdayQuery =
            new IpcDataCache.QueryHandler<User, Birthday>() {
                @Override
                public Birthday apply(User user) {
                    return mService.getBirthday(user);
                }
            };
    private static final int BDAY_CACHE_MAX = 8;  // Maximum birthdays to cache
    private static final String BDAY_API = "getUserBirthday";
    private final IpcDataCache<User, Birthday> mCache
            new IpcDataCache<User, Birthday>(
                BDAY_CACHE_MAX, MODULE_SYSTEM, BDAY_API,  BDAY_API, mBirthdayQuery);

    /** @hide **/
    @VisibleForTesting
    public static void clearCache() {
        IpcDataCache.invalidateCache(MODULE_SYSTEM, BDAY_API);
    }

    public Birthday getBirthday(User user) {
        return mCache.query(user);
    }
}

একটি সম্পূর্ণ উদাহরণের জন্য, android.app.admin.DevicePolicyManager দেখুন।

IpcDataCache মেইনলাইন মডিউল সহ সমস্ত সিস্টেম কোডের জন্য উপলব্ধ। এছাড়াও PropertyInvalidatedCache আছে যা প্রায় অভিন্ন, কিন্তু শুধুমাত্র ফ্রেমওয়ার্কে দৃশ্যমান। সম্ভব হলে IpcDataCache পছন্দ করুন।

সার্ভার-সাইড পরিবর্তনে ক্যাশে অবৈধ করুন

যদি সার্ভার থেকে প্রত্যাবর্তিত মান সময়ের সাথে পরিবর্তিত হতে পারে, পরিবর্তনগুলি পর্যবেক্ষণের জন্য একটি কলব্যাক প্রয়োগ করুন এবং একটি কলব্যাক নিবন্ধন করুন যাতে আপনি সেই অনুযায়ী ক্লায়েন্ট-সাইড ক্যাশে বাতিল করতে পারেন।

ইউনিট পরীক্ষার ক্ষেত্রে ক্যাশে অবৈধ করুন

একটি ইউনিট টেস্ট স্যুটে, আপনি বাস্তব সার্ভারের পরিবর্তে ক্লায়েন্ট কোডটি একটি পরীক্ষার দ্বিগুণের বিরুদ্ধে পরীক্ষা করতে পারেন। যদি তাই হয়, তাহলে পরীক্ষার ক্ষেত্রের মধ্যে যেকোনো ক্লায়েন্ট-সাইড ক্যাশে সাফ করতে ভুলবেন না। এটি হল টেস্ট কেসকে পারস্পরিক হারমেটিক রাখা এবং একটি টেস্ট কেস অন্যটিতে হস্তক্ষেপ করা থেকে বিরত রাখা।

@RunWith(AndroidJUnit4.class)
public class BirthdayManagerTest {

    @Before
    public void setUp() {
        BirthdayManager.clearCache();
    }

    @After
    public void tearDown() {
        BirthdayManager.clearCache();
    }

    ...
}

CTS পরীক্ষা লেখার সময় যেগুলি একটি API ক্লায়েন্ট ব্যবহার করে যা অভ্যন্তরীণভাবে ক্যাশিং ব্যবহার করে, ক্যাশে হল একটি বাস্তবায়নের বিশদ যা API লেখকের কাছে প্রকাশ করা হয় না, তাই CTS পরীক্ষার জন্য ক্লায়েন্ট কোডে ব্যবহৃত ক্যাশিং সম্পর্কে কোনো বিশেষ জ্ঞানের প্রয়োজন হয় না।

ক্যাশে হিট এবং মিস অধ্যয়ন

IpcDataCache এবং PropertyInvalidatedCache লাইভ পরিসংখ্যান মুদ্রণ করতে পারে:

adb shell dumpsys cacheinfo
  ...
  Cache Name: cache_key.is_compat_change_enabled
    Property: cache_key.is_compat_change_enabled
    Hits: 1301458, Misses: 21387, Skips: 0, Clears: 39
    Skip-corked: 0, Skip-unset: 0, Skip-bypass: 0, Skip-other: 0
    Nonce: 0x856e911694198091, Invalidates: 72, CorkedInvalidates: 0
    Current Size: 1254, Max Size: 2048, HW Mark: 2049, Overflows: 310
    Enabled: true
  ...

ক্ষেত্র

হিট:

  • সংজ্ঞা: ক্যাশের মধ্যে কতবার অনুরোধ করা ডেটা সফলভাবে পাওয়া গেছে।
  • তাত্পর্য: অপ্রয়োজনীয় ডেটা পুনরুদ্ধার হ্রাস করে, একটি দক্ষ এবং দ্রুত ডেটা পুনরুদ্ধার নির্দেশ করে।
  • উচ্চ গণনা সাধারণত ভাল হয়.

সাফ:

  • সংজ্ঞা: অবৈধ হওয়ার কারণে ক্যাশে কতবার সাফ করা হয়েছে।
  • সাফ করার কারণ:
    • অবৈধকরণ: সার্ভার থেকে পুরানো ডেটা।
    • স্পেস ম্যানেজমেন্ট: ক্যাশে পূর্ণ হলে নতুন ডেটার জন্য জায়গা তৈরি করা।
  • উচ্চ গণনা ঘন ঘন ডেটা পরিবর্তন এবং সম্ভাব্য অদক্ষতা নির্দেশ করতে পারে।

মিস:

  • সংজ্ঞা: যতবার ক্যাশে অনুরোধ করা ডেটা প্রদান করতে ব্যর্থ হয়েছে।
  • কারণ:
    • অদক্ষ ক্যাশে: ক্যাশে খুব ছোট বা সঠিক ডেটা সংরক্ষণ করা হয় না।
    • ঘন ঘন তথ্য পরিবর্তন.
    • প্রথমবার অনুরোধ.
  • উচ্চ গণনা সম্ভাব্য ক্যাশিং সমস্যার পরামর্শ দেয়।

এড়িয়ে যায়:

  • সংজ্ঞা: উদাহরণ যেখানে ক্যাশে ব্যবহার করা হয়নি, যদিও এটি হতে পারে।
  • এড়িয়ে যাওয়ার কারণ:
    • কর্কিং: অ্যান্ড্রয়েড প্যাকেজ ম্যানেজার আপডেটের জন্য নির্দিষ্ট, বুট করার সময় প্রচুর পরিমাণে কলের কারণে ইচ্ছাকৃতভাবে ক্যাশিং বন্ধ করা।
    • আনসেট: ক্যাশে বিদ্যমান কিন্তু আরম্ভ করা হয়নি। নন্সটি আনসেট ছিল, যার মানে ক্যাশে কখনও অবৈধ হয়নি।
    • বাইপাস: ক্যাশে এড়িয়ে যাওয়ার ইচ্ছাকৃত সিদ্ধান্ত।
  • উচ্চ গণনা ক্যাশে ব্যবহারের সম্ভাব্য অদক্ষতা নির্দেশ করে।

বাতিল করে:

  • সংজ্ঞা: ক্যাশে করা ডেটাকে সেকেলে বা বাসি হিসাবে চিহ্নিত করার প্রক্রিয়া।
  • তাৎপর্য: একটি সংকেত প্রদান করে যে সিস্টেমটি সবচেয়ে আপ-টু-ডেট ডেটার সাথে কাজ করে, ত্রুটি এবং অসঙ্গতি প্রতিরোধ করে।
  • সাধারণত ডেটার মালিক সার্ভার দ্বারা ট্রিগার করা হয়।

বর্তমান আকার:

  • সংজ্ঞা: ক্যাশে উপাদানের বর্তমান পরিমাণ।
  • তাৎপর্য: ক্যাশের সম্পদের ব্যবহার এবং সিস্টেম কর্মক্ষমতার উপর সম্ভাব্য প্রভাব নির্দেশ করে।
  • উচ্চতর মান সাধারণত ক্যাশে দ্বারা আরো মেমরি ব্যবহার করা হয় মানে.

সর্বোচ্চ আকার:

  • সংজ্ঞা: ক্যাশে জন্য বরাদ্দ স্থান সর্বোচ্চ পরিমাণ.
  • তাৎপর্য: ক্যাশের ক্ষমতা এবং ডেটা সঞ্চয় করার ক্ষমতা নির্ধারণ করে।
  • একটি উপযুক্ত সর্বোচ্চ আকার সেট করা মেমরি ব্যবহারের সাথে ক্যাশে কার্যকারিতা ভারসাম্য করতে সহায়তা করে। একবার সর্বাধিক আকারে পৌঁছে গেলে, সাম্প্রতিকভাবে ব্যবহৃত উপাদানটিকে উচ্ছেদ করে একটি নতুন উপাদান যুক্ত করা হয়, যা অদক্ষতা নির্দেশ করতে পারে।

উচ্চ জল চিহ্ন:

  • সংজ্ঞা: ক্যাশে তৈরির পর থেকে সর্বোচ্চ যে আকারে পৌঁছেছে।
  • তাৎপর্য: সর্বোচ্চ ক্যাশে ব্যবহার এবং সম্ভাব্য মেমরি চাপের অন্তর্দৃষ্টি প্রদান করে।
  • উচ্চ জলের চিহ্ন নিরীক্ষণ অপ্টিমাইজেশনের জন্য সম্ভাব্য বাধা বা এলাকা সনাক্ত করতে সাহায্য করতে পারে।

ওভারফ্লো:

  • সংজ্ঞা: যতবার ক্যাশে তার সর্বোচ্চ আকার অতিক্রম করেছে এবং নতুন এন্ট্রির জন্য জায়গা তৈরি করতে ডেটা উচ্ছেদ করতে হয়েছে।
  • তাৎপর্য: তথ্য উচ্ছেদের কারণে ক্যাশে চাপ এবং সম্ভাব্য কর্মক্ষমতা হ্রাস নির্দেশ করে।
  • উচ্চ ওভারফ্লো গণনা প্রস্তাব করে যে ক্যাশের আকার সামঞ্জস্য করা বা ক্যাশিং কৌশল পুনর্মূল্যায়নের প্রয়োজন হতে পারে।

একই পরিসংখ্যান একটি বাগ রিপোর্টেও পাওয়া যাবে।

ক্যাশের আকার টিউন করুন

ক্যাশে একটি সর্বোচ্চ আকার আছে. যখন ক্যাশের সর্বোচ্চ আকার অতিক্রম করা হয়, তখন এন্ট্রিগুলিকে LRU ক্রমে উচ্ছেদ করা হয়।

  • খুব কম এন্ট্রি ক্যাশে করা ক্যাশে হিট রেটকে নেতিবাচকভাবে প্রভাবিত করতে পারে।
  • অনেকগুলি এন্ট্রি ক্যাশে করা ক্যাশের মেমরি ব্যবহার বাড়ায়।

আপনার ব্যবহারের ক্ষেত্রে সঠিক ব্যালেন্স খুঁজুন।

অপ্রয়োজনীয় ক্লায়েন্ট কল বাদ দিন

ক্লায়েন্টরা অল্প সময়ের মধ্যে একাধিকবার সার্ভারে একই প্রশ্ন করতে পারে:

public void executeAll(List<Operation> operations) throws SecurityException {
    for (Operation op : operations) {
        for (Permission permission : op.requiredPermissions()) {
            if (!permissionChecker.checkPermission(permission, ...)) {
                throw new SecurityException("Missing permission " + permission);
            }
        }
        op.execute();
  }
}

পূর্ববর্তী কলগুলির ফলাফলগুলি পুনরায় ব্যবহার করার কথা বিবেচনা করুন:

public void executeAll(List<Operation> operations) throws SecurityException {
    Set<Permission> permissionsChecked = new HashSet<>();
    for (Operation op : operations) {
        for (Permission permission : op.requiredPermissions()) {
            if (!permissionsChecked.add(permission)) {
                if (!permissionChecker.checkPermission(permission, ...)) {
                    throw new SecurityException(
                            "Missing permission " + permission);
                }
            }
        }
        op.execute();
  }
}

সাম্প্রতিক সার্ভার প্রতিক্রিয়ার ক্লায়েন্ট-সাইড মেমোাইজেশন বিবেচনা করুন

ক্লায়েন্ট অ্যাপ্লিকেশানগুলি API এর সার্ভার থেকে অর্থপূর্ণভাবে নতুন প্রতিক্রিয়া তৈরি করতে পারে তার চেয়ে দ্রুত গতিতে API-কে জিজ্ঞাসা করতে পারে৷ এই ক্ষেত্রে, একটি কার্যকর পদ্ধতি হল একটি টাইমস্ট্যাম্প সহ ক্লায়েন্ট সাইডে শেষ দেখা সার্ভারের প্রতিক্রিয়া মেমোাইজ করা এবং যদি মেমোকৃত ফলাফলটি যথেষ্ট সাম্প্রতিক হয় তবে সার্ভারকে জিজ্ঞাসা না করেই মেমোাইজ করা ফলাফল ফিরিয়ে দেওয়া। API ক্লায়েন্ট লেখক মেমোাইজেশন সময়কাল নির্ধারণ করতে পারেন।

উদাহরণস্বরূপ, প্রতিটি ফ্রেমে পরিসংখ্যানের জন্য অনুসন্ধান করে একটি অ্যাপ ব্যবহারকারীর কাছে নেটওয়ার্ক ট্র্যাফিক পরিসংখ্যান প্রদর্শন করতে পারে:

@UiThread
private void setStats() {
    mobileRxBytesTextView.setText(
        Long.toString(TrafficStats.getMobileRxBytes()));
    mobileRxPacketsTextView.setText(
        Long.toString(TrafficStats.getMobileRxPackages()));
    mobileTxBytesTextView.setText(
        Long.toString(TrafficStats.getMobileTxBytes()));
    mobileTxPacketsTextView.setText(
        Long.toString(TrafficStats.getMobileTxPackages()));
}

অ্যাপটি 60 Hz এ ফ্রেম আঁকতে পারে। কিন্তু অনুমানগতভাবে, TrafficStats এ ক্লায়েন্ট কোড প্রতি সেকেন্ডে সর্বাধিক একবার পরিসংখ্যানের জন্য সার্ভারকে জিজ্ঞাসা করতে বেছে নিতে পারে, এবং যদি পূর্ববর্তী প্রশ্নের এক সেকেন্ডের মধ্যে জিজ্ঞাসা করা হয়, শেষ দেখা মানটি ফেরত দেয়। এটি অনুমোদিত কারণ API ডকুমেন্টেশন ফলাফলের সতেজতা সম্পর্কিত কোনো চুক্তি প্রদান করে না।

participant App code as app
participant Client library as clib
participant Server as server

app->clib: request @ T=100ms
clib->server: request
server->clib: response 1
clib->app: response 1

app->clib: request @ T=200ms
clib->app: response 1

app->clib: request @ T=300ms
clib->app: response 1

app->clib: request @ T=2000ms
clib->server: request
server->clib: response 2
clib->app: response 2

সার্ভার প্রশ্নের পরিবর্তে ক্লায়েন্ট-সাইড কোডজেন বিবেচনা করুন

বিল্ড টাইমে সার্ভারের কাছে কোয়েরির ফলাফল জানা থাকলে, বিল্ড টাইমে ক্লায়েন্টের কাছে জ্ঞাত কিনা তা বিবেচনা করুন এবং ক্লায়েন্ট সাইডে এপিআই সম্পূর্ণরূপে প্রয়োগ করা যেতে পারে কিনা তা বিবেচনা করুন।

নিম্নলিখিত অ্যাপ কোডটি বিবেচনা করুন যা ডিভাইসটি ঘড়ি কিনা তা পরীক্ষা করে (অর্থাৎ, ডিভাইসটিতে Wear OS চলছে):

public boolean isWatch(Context ctx) {
    PackageManager pm = ctx.getPackageManager();
    return pm.hasSystemFeature(PackageManager.FEATURE_WATCH);
}

ডিভাইসের এই বৈশিষ্ট্যটি বিল্ড টাইমে পরিচিত, বিশেষত সেই সময়ে যখন এই ডিভাইসের বুট ইমেজের জন্য ফ্রেমওয়ার্ক তৈরি করা হয়েছিল। hasSystemFeature জন্য ক্লায়েন্ট-সাইড কোড দূরবর্তী PackageManager সিস্টেম পরিষেবার জন্য অনুসন্ধান না করে অবিলম্বে একটি পরিচিত ফলাফল ফিরিয়ে দিতে পারে।

ক্লায়েন্টে সার্ভার কলব্যাকগুলিকে ডিডুপ্লিকেট করুন৷

সবশেষে, API ক্লায়েন্ট ঘটনা সম্পর্কে অবহিত হওয়ার জন্য API সার্ভারের সাথে কলব্যাক নিবন্ধন করতে পারে।

একই অন্তর্নিহিত তথ্যের জন্য একাধিক কলব্যাক নিবন্ধন করা অ্যাপগুলির জন্য সাধারণ। IPC ব্যবহার করে নিবন্ধিত কলব্যাক প্রতি একবার সার্ভার ক্লায়েন্টকে অবহিত করার পরিবর্তে, ক্লায়েন্ট লাইব্রেরিতে সার্ভারের সাথে IPC ব্যবহার করে একটি নিবন্ধিত কলব্যাক থাকা উচিত এবং তারপরে অ্যাপে প্রতিটি নিবন্ধিত কলব্যাককে অবহিত করা উচিত।

digraph d_front_back {
  rankdir=RL;
  node [style=filled, shape="rectangle", fontcolor="white" fontname="Roboto"]
  server->clib
  clib->c1;
  clib->c2;
  clib->c3;

  subgraph cluster_client {
    graph [style="dashed", label="Client app process"];
    c1 [label="my.app.FirstCallback" color="#4285F4"];
    c2 [label="my.app.SecondCallback" color="#4285F4"];
    c3 [label="my.app.ThirdCallback" color="#4285F4"];
    clib [label="android.app.FooManager" color="#F4B400"];
  }

  subgraph cluster_server {
    graph [style="dashed", label="Server process"];
    server [label="com.android.server.FooManagerService" color="#0F9D58"];
  }
}