API Over-fetching and Under-fetching

মডার্ন ক্লায়েন্ট-সার্ভার আর্কিটেকচারে ক্লায়েন্ট (যেমন মোবাইল অ্যাপ বা ওয়েব ফ্রন্টএন্ড) এবং ব্যাকএন্ড সার্ভারের মধ্যে ডেটা আদান-প্রদানের মূল মাধ্যম হলো API (Application Programming Interface)। ঐতিহ্যগত RESTful আর্কিটেকচারে রিসোর্স-ভিত্তিক এন্ডপয়েন্ট ডিজাইন করা হয়, যার ফলে ডেটা ফেচিংয়ের ক্ষেত্রে দুটি বড় আর্কিটেকচারাল সীমাবদ্ধতা দেখা দেয়: Over-fetching এবং Under-fetching।

এই সমস্যাগুলো কেবল অপ্রয়োজনীয় নেটওয়ার্ক ট্র্যাফিকই তৈরি করে না, বরং হাই-স্কেল সিস্টেমে ক্লায়েন্টের মেমোরি কনসাম্পশন, ডিসিরিয়ালাইজেশন ওভারহেড, ডাটাবেজ I/O লোড এবং সামগ্রিক লেটেন্সি উল্লেখযোগ্যভাবে বাড়িয়ে দেয়।

Over-fetching: অপ্রয়োজনীয় ডেটা

Over-fetching হলো এমন একটি অবস্থা যেখানে একটি নির্দিষ্ট ক্লায়েন্ট ইন্টারফেসের প্রয়োজনের তুলনায় API রেসপন্সে অনেক বেশি অ্যাট্রিবিউট বা ডেটা ফেরত পাঠানো হয়।

RESTful ডিজাইনে একটি সাধারণ প্যাটার্ন হলো একটি একক এন্ডপয়েন্ট (যেমন GET /api/v1/users/{id}) পুরো রিসোর্স রিপ্রেজেন্টেশন রিটার্ন করে। এটি ডেস্কটপ ড্যাশবোর্ডের জন্য উপযুক্ত হলেও, কোনো লাইটওয়েট মোবাইল উইজেট বা প্রোফাইল হেডারের জন্য মারাত্মক অপচয়।

নেতিবাচক প্রভাব:

  • ব্যান্ডউইথ অপচয়: বিশেষ করে মোবাইল ডাটা নেটওয়ার্কে অতিরিক্ত পে-লোড ট্রান্সফার ধীরগতির কারণ হয়।

  • CPU ও ব্যাটারি ড্রেন: ক্লায়েন্ট ডিভাইসে বিশাল JSON পে-লোড পার্স (JSON parsing/deserialization) করতে অতিরিক্ত মেমোরি ও CPU খরচ হয়।

  • সার্ভার ও ডাটাবেজ ওভারহেড: ব্যাকএন্ডকে ডাটাবেজ থেকে আনপ্রয়োজনীয় কলাম কোয়েরি করতে হয় এবং সেগুলোকে মেমোরিতে সিরিয়ালাইজ করতে হয়।

কোড উদাহরণ: প্রথাগত ওভার-ফেচিং রেসপন্স (Python / FastAPI)

নিচের ব্যাকএন্ড ইমপ্লিমেন্টেশনে লক্ষ্য করা যায় যে ক্লায়েন্টের কেবল নাম ও ছবি দরকার হলেও সার্ভার সম্পূর্ণ ইউজার অবজেক্ট এবং রিলেটেড হিস্ট্রি পাঠাচ্ছে:

from typing import List, Optional
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class UserFullProfileResponse(BaseModel):
    id: int
    name: str
    email: str
    phone: str
    profile_picture: str
    shipping_address: str
    order_history: List[dict]
    wishlist: List[dict]


@app.get("/api/v1/users/{user_id}", response_model=UserFullProfileResponse)
def get_user_profile(user_id: int):
    # Simulating database record fetching
    return {
        "id": user_id,
        "name": "Farjanul Nayem",
        "email": "nayem@example.com",
        "phone": "+8801700000000",
        "profile_picture": "https://cdn.example.com/avatars/user_101.jpg",
        "shipping_address": "House 12, Road 5, Dhanmondi, Dhaka",
        "order_history": [
            {"order_id": 901, "total": 120.50},
            {"order_id": 902, "total": 45.00},
        ],
        "wishlist": [{"item_id": 401, "item_name": "Mechanical Keyboard"}],
    }

Output

{
  "id": 101,
  "name": "Farjanul Nayem",
  "email": "nayem@example.com",
  "phone": "+8801700000000",
  "profile_picture": "https://cdn.example.com/avatars/user_101.jpg",
  "shipping_address": "House 12, Road 5, Dhanmondi, Dhaka",
  "order_history": [{"order_id": 901, "total": 120.5}],
  "wishlist": [{"item_id": 401, "item_name": "Mechanical Keyboard"}]
}

Under-fetching: অপর্যাপ্ত ডেটা ও N+1 রিকোয়েস্টের সমস্যা

Under-fetching ঘটে যখন একটি নির্দিষ্ট ক্লায়েন্ট ভিউ রেন্ডার করার জন্য একটি এন্ডপয়েন্ট থেকে যথেষ্ট তথ্য পাওয়া যায় না। ফলে ক্লায়েন্টকে নির্ভরশীল ডেটা সংগ্রহের জন্য এক বা একাধিক ধারাবাহিক (sequential) বা সমান্তরাল (parallel) নেটওয়ার্ক রিকোয়েস্ট করতে হয়।

এই সমস্যাটি বহুল পরিচিত N+1 Network Waterfall Problem তৈরি করে।

নেতিবাচক প্রভাব:

  • হাই লেটেন্সি (High Round-Trip Time - RTT): ক্লায়েন্টকে প্রথম রিকোয়েস্টের রেসপন্সের ওপর ভিত্তি করে পরবর্তী রিকোয়েস্ট পাঠাতে হয়। মোবাইল নেটওয়ার্কে প্রতিটি নেটওয়ার্ক হ্যান্ডশেক ও লেটেন্সি রেন্ডারিং সময় বহুগুণ বাড়িয়ে দেয়।

  • ব্যাটারি ও সকেট কনসাম্পশন: ঘনঘন কানেকশন ওপেন ও ক্লোজ করার ফলে ডিভাইসের রেডিও পাওয়ার বেশি খরচ হয়।

  • সার্ভারে রিকোয়েস্ট স্পাইক: প্রতি পেজ লোডে একটির জায়গায় ৩-৪টি রিকোয়েস্ট আসার ফলে ব্যাকএন্ড থ্রুপুট কমে যায়।

কোড উদাহরণ: ক্লায়েন্টে N+1 প্রবলেম (Java)

নিচের জাভা ক্লায়েন্ট কোডটিতে দেখা যাচ্ছে পোস্ট আনার পর আবার অথরের ডেটা আনার জন্য আরেকটি ব্লকিং নেটওয়ার্ক কল করা লাগছে:

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class BlogClientService {

    private final HttpClient httpClient = HttpClient.newHttpClient();
    private static final String BASE_URL = "https://api.example.com/v1";

    public void fetchPostWithAuthor(int postId) throws Exception {
        // First Network Request: Fetch Post
        HttpRequest postRequest = HttpRequest.newBuilder()
                .uri(URI.create(BASE_URL + "/posts/" + postId))
                .GET()
                .build();

        HttpResponse<String> postResponse = httpClient.send(postRequest, HttpResponse.BodyHandlers.ofString());
        System.out.println("Post Response: " + postResponse.body());

        // Simulated extraction of author_id from JSON (e.g., author_id = 7)
        int authorId = 7;

        // Second Network Request: Fetch Author (Cascading Waterfall)
        HttpRequest authorRequest = HttpRequest.newBuilder()
                .uri(URI.create(BASE_URL + "/users/" + authorId))
                .GET()
                .build();

        HttpResponse<String> authorResponse = httpClient.send(authorRequest, HttpResponse.BodyHandlers.ofString());
        System.out.println("Author Response: " + authorResponse.body());
    }
}

সমাধান ও আর্কিটেকচারাল প্যাটার্ন

Over-fetching এবং Under-fetching সমাধানের জন্য কেবল ফ্রেমওয়ার্ক পরিবর্তনই যথেষ্ট নয়, ব্যাকএন্ড আর্কিটেকচারে নির্দিষ্ট কিছু প্যাটার্ন প্রয়োগ করা যায়:

সমস্যা

প্যাটার্ন / সমাধান

মেকানিজম

Over-fetch

Sparse Fieldsets

কুয়েরি প্যারামিটারে ফিল্ড ডিফাইন করা (যেমন: ?fields=name,profile_picture)

Over-fetch

GraphQL / tRPC

ডিক্লেয়ারেটিভ ডাটা ফেচিং—ক্লায়েন্ট ঠিক যা চাইবে, সার্ভার কেবল সেটুকুই পাঠাবে

Under-fetch

Composite / BFF Pattern

ফ্রন্টএন্ডের জন্য নির্দিষ্ট গেটওয়ে যা ইন্টারনাল মাইক্রোসার্ভিস থেকে ডেটা এগ্রিগেট করে একটি রেসপন্স দেয়

Under-fetch

Compound Documents (JSON:API)

রিলেটেড রিসোর্স একই পে-লোডে include প্যারামিটার দিয়ে এমবেড করা (যেমন: ?include=author)

সমাধান ১: REST API-তে Sparse Fieldsets ও Compound Resources (Python)

GraphQL-এ মাইগ্রেট না করেও পিওর REST-এ কুয়েরি প্যারামিটার ও ডাইনামিক ফিল্টারিং দিয়ে উভয় সমস্যা মেটানো সম্ভব:

from typing import List, Optional
from fastapi import FastAPI, Query
from pydantic import BaseModel

app = FastAPI()


class Author(BaseModel):
    id: int
    name: str
    avatar_url: str


class PostResponse(BaseModel):
    id: int
    title: str
    content: str
    author: Optional[Author] = None


@app.get("/api/v1/posts/{post_id}")
def get_post(
    post_id: int,
    include: Optional[str] = Query(None, description="Related entities to include"),
    fields: Optional[str] = Query(None, description="Comma-separated field list"),
):
    # Base resource
    post_data = {
        "id": post_id,
        "title": "Architecting Scalable APIs",
        "content": "Deep dive into network performance...",
    }

    # Handling Under-fetching: Include author directly to avoid secondary call
    if include and "author" in include.split(","):
        post_data["author"] = {
            "id": 7,
            "name": "Jane Doe",
            "avatar_url": "https://cdn.example.com/avatars/7.png",
        }

    # Handling Over-fetching: Filter out unrequested fields
    if fields:
        requested_fields = set(fields.split(","))
        post_data = {k: v for k, v in post_data.items() if k in requested_fields}

    return post_data

সমাধান ২: GraphQL-এর ডিক্লেয়ারেটিভ সমাধান

GraphQL-এর মূল শক্তি হলো সিঙ্গেল রাউন্ড-ট্রিপে সুনির্দিষ্ট ডেটা রিটার্ন করা।

নিচে একটি আদর্শ GraphQL কোয়েরি দেখানো হলো যা একাধারে Over-fetch (অতিরিক্ত ফিল্ড বাদ দিয়ে) এবং Under-fetch (একই কলে পোস্ট ও অথর ডেটা এনে) দূর করে:

query GetPostDetails($id: ID!) {
  post(id: $id) {
    title
    content
    author {
      name
      profilePicture
    }
  }
}

Output

{
  "data": {
    "post": {
      "title": "Architecting Scalable APIs",
      "content": "Deep dive into network performance...",
      "author": {
        "name": "Jane Doe",
        "profilePicture": "https://cdn.example.com/avatars/7.png"
      }
    }
  }
}

একটি হাই-পারফরম্যান্স সিস্টেমে API কন্ট্রাক্ট ডিজাইনের সময়ই ফ্রন্টএন্ড এবং ক্লায়েন্টের ব্যবহারের ধরন বিশ্লেষণ করা আবশ্যক। সিম্পল আর্কিটেকচারে REST-এর ওপর fields ও embed প্যারামিটার দিয়ে শুরু করা যায়; তবে মাল্টি-প্ল্যাটফর্ম বা হাইলি গ্রাফ-বেসড ইন্টারকানেক্টেড ডেটার ক্ষেত্রে GraphQL অথবা Backend-For-Frontend (BFF) প্যাটার্ন গ্রহণ করা সবচেয়ে টেকসই সিদ্ধান্ত।