طراحی pagination API: offset، cursor و ترتیب پایدار
7 دقیقه مطالعه

pagination، filter و sort را در API پرترافیک طراحی کنید: offset یا cursor، ترتیب پایدار، توکن مات و سقف اندازهٔ صفحه؛ بر پایهٔ AIP-158 و مستندات PostgreSQL.
pagination اولین چیزی است که در API پرترافیک از کنترل خارج میشود: صفحههای عمیق کند میشوند، نتایج بین دو درخواست جابهجا میشوند و مصرفکنندهها یک کلاینت «همه را بگیر» مینویسند که دیتابیس را خسته میکند. این مقاله طراحی pagination، ترتیب پایدار، filter و sort را با تکیه بر راهنمای Google AIP-158، توضیح «No Offset» از Use The Index, Luke، مستندات PostgreSQL و مستندات pagination در Stripe میآورد (مراجعهٔ ۱۹ اکتبر ۲۰۲۶). ما سرویسی نساختهایم و هیچ عدد سرعتی ادعا نمیکنیم؛ نتیجه به داده، شاخصها و دیتابیس شما بستگی دارد. برای مفهوم API API چیست و معماری انتخاب معماری وباپ را ببینید.
از ابتدا pagination بگذارید
AIP-158 میگوید endpointهای برگردانندهی مجموعه باید از ابتدا pagination داشته باشند چون افزودن آن به متد موجود تغییر ناسازگار است؛ مجموعهها بزرگ میشوند و زمان جستوجو و اندازهٔ پاسخ را بالا میبرند. اگر امروز endpoint همه چیز را برمیگرداند، حتی برای «فقط ده مورد» هم از همین الان حد بگذارید (برای مهاجرت مقالهٔ نسخهبندی API را ببینید).
offset یا cursor؟
| offset/limit | cursor (keyset) | |
|---|---|---|
| سادگی | خیلی ساده، پرش به صفحهٔ دلخواه | پیچیدهتر، فقط «بعدی/قبلی» |
| هزینهٔ صفحهٔ عمیق | ردیفهای ردشده باید خوانده شوند؛ OFFSET بزرگ ممکن است ناکارآمد باشد (PostgreSQL) | شرط روی آخرین مقدار دیدهشده؛ مستقل از عمق (Use The Index, Luke) |
| پایداری هنگام تغییر داده | درج بین دو درخواست باعث تکرار یا جاافتادگی میشود | جابهجایی نتایج را کم میکند |
| نیاز | ORDER BY پایدار | ORDER BY پایدار و ستونهای یکتا در ترتیب |
Use The Index, Luke توضیح میدهد offset فقط تعداد ردیفهای حذفشونده را میداند و بدون زمینه، پایگاه داده همانقدر ردیف را میخواند و دور میریزد؛ و اگر ردیفی بین دو درخواست درج شود، صفحهٔ بعد ردیف تکراری میدهد. جایگزین، روش seek یا keyset است: یک شرط WHERE که فقط دادهای را انتخاب میکند که هنوز ندیدهاید، مثلاً id < ?last_seen_id ORDER BY id DESC، و این روش میتواند حتی سریعتر باشد. مستندات PostgreSQL هم میگوید OFFSET بزرگ ممکن است ناکارآمد باشد چون ردیفهای ردشده باید در سرور محاسبه شوند.
قاعدهٔ انتخاب (جمعبندی ما): برای مجموعههای بزرگ و پرتغییر یا فید بیپایان cursor؛ برای فهرستهای کوچک و ثابت یا جاهایی که «برو صفحهٔ ۷» لازم است offset قابلقبول است، به شرط سقف عمق. هیچکدام مطلق نیست و باید با داده و شاخصهای خودتان بسنجید.

ترتیب پایدار
مستندات PostgreSQL صریح است: استفاده از مقادیر مختلف LIMIT و OFFSET بدون ORDER BY نتایج ناسازگار میدهد چون SQL بدون ORDER BY ترتیبی تضمین نمیکند؛ این باگ نیست و نتیجهٔ طبیعی ماهیت SQL است. پس: هر endpoint صفحهبندیشده یک ترتیب مشخص دارد و ستونهایش یکتا هستند. اگر بر اساس «تاریخ ایجاد» مرتب میکنید و چند ردیف تاریخ یکسان دارند، ستون یکتا (مثل id) را بهعنوان tie-breaker اضافه کنید وگرنه بین صفحهها ردیف جا میافتد یا تکرار میشود. برای sort توسط کاربر، فقط روی ستونهایی اجازه بدهید که شاخص دارند.
طراحی پارامترها و پاسخ
- اندازهٔ صفحه: AIP-158 میگوید اندازهٔ صفحه اختیاری باشد، پیشفرض مستند داشته باشد، مقدار بزرگتر از سقف به سقف کاهش یابد و مقدار منفی خطا بدهد؛ و API ممکن است کمتر از درخواست برگرداند (حتی صفر) بدون اینکه به انتهای مجموعه رسیده باشد. Stripe نمونهای از سیاست مستند را دارد:
limitبین ۱ و ۱۰۰ با پیشفرض ۱۰ در API نسخهٔ v1 (مقادیر خود Stripe است، توصیهٔ ما نیست). - توکن صفحهٔ بعد: AIP-158 توکن را «مات» (opaque) میداند و میگوید باقی پارامترها باید با درخواستی که توکن را صادر کرد یکی باشند؛ اگر فرق کنند خطا بدهید. مات بودن یعنی کلاینت ساختار آن را نمیفهمد و شما بعداً میتوانید آن را تغییر دهید.
- نشانهٔ پایان: AIP-158 میگوید توکن بعدی خالی تنها راه اعلام پایان مجموعه است. Stripe از
has_moreو شناسههایstarting_afterوending_beforeاستفاده میکند. - مجموع: total_size اختیاری است و میتواند تخمین باشد (باید مستند شود). شمارش دقیق روی جدول بزرگ گران است؛ در صورت نیاز تخمین یا بدون مجموع.
محدودیتها
- سقف اندازهٔ صفحه را سمت سرور اعمال کنید و مقدار را مستند کنید؛ مقدار خاص تیم شما را با اندازهٔ پاسخ و زمان واقعی تعیین کنید.
- برای offset سقف عمق بگذارید (مثلاً بیشتر از N رد نکن) یا کاربر را به cursor هدایت کنید.
- برای filterها فقط روی ستونهای دارای شاخص اجازه بدهید و فهرست مجاز را مستند کنید.
- پیمایش کامل (export) را به endpoint یا کار جداگانه (کار پسزمینه) منتقل کنید تا ترافیک عادی را تحت تأثیر نگذارد.
- انقضای توکن را در مستندات بگویید؛ AIP-158 بحث توکنهای منقضیشونده را دارد.
آزمون
- دادهای با ترتیب یکسان در چند ردیف (مثلاً تاریخ مشابه) بسازید و همهٔ صفحهها را پیمایش کنید؛ باید هر ردیف دقیقاً یک بار بیاید.
- در میانهٔ پیمایش یک ردیف جدید درج کنید و رفتار offset را با cursor مقایسه کنید.
- اندازهٔ صفحهٔ بزرگتر از سقف و منفی را بفرستید و پاسخ را با مستندات تطبیق دهید.
- زمان صفحهٔ اول و صفحهٔ عمیق را روی حجم داده نزدیک تولید اندازه بگیرید و با EXPLAIN شاخص را بررسی کنید.
- تغییر پارامتر هنگام استفاده از توکن را بفرستید و خطا را ببینید.
مثال ساختگی: فهرست سفارشها
فرض کنید endpoint فهرست سفارشها بر اساس تاریخ ایجاد و سپس شناسه (برای شکستن تساوی) مرتب شده است. درخواست اول بدون توکن میآید و N ردیف برمیگردد (N همان اندازهٔ پیشفرض مستند شما)؛ پاسخ شامل فهرست و یک توکن مات برای صفحهٔ بعد است. داخل توکن، مقدار تاریخ و شناسهٔ آخرین ردیف رمزگذاری یا کدگذاری شده، و سرور برای صفحهٔ بعد شرطی مثل «تاریخ کمتر، یا تاریخ برابر و شناسه کمتر» را اعمال میکند. اگر کلاینت همراه توکن، filter متفاوتی بفرستد، سرور خطای پارامتر ناسازگار میدهد. این طراحی کمک میکند ردیف تکراری یا جاافتاده نداشته باشید، ولی باید با آزمون روی دادهای شبیه تولید و بررسی شاخص مرکب (تاریخ و شناسه) تأیید شود. مثال آموزشی است و عددی ندارد.
اگر محصول به «رفتن به صفحهٔ ۷» نیاز دارد، میتوانید offset را برای فهرستهای کوچک و ثابت نگه دارید و برای فهرستهای بزرگ cursor ارائه دهید؛ ولی دو رفتار را در مستندات شفاف جدا کنید تا مصرفکننده بداند هر endpoint چه رفتاری دارد.
اشتباهات رایج
- نداشتن ORDER BY یا نداشتن ستون یکتا در ترتیب.
- offset بدون سقف عمق روی جدول بزرگ.
- برگرداندن مجموع دقیق گران برای هر صفحه.
- افشای ساختار توکن و وابسته شدن کلاینت به آن.
- افزودن pagination بعد از انتشار بدون مهاجرت.
- اجازهٔ sort و filter روی ستون بدون شاخص.
- استفاده از یک endpoint برای export کامل.
چکلیست
- هر مجموعه از ابتدا pagination دارد.
- ترتیب پایدار با ستون یکتا تعریف شده است.
- نوع pagination (offset یا cursor) با دلیل انتخاب شده است.
- سقف اندازهٔ صفحه و عمق مستند و اعمال شده است.
- توکن مات است و پارامترها با آن سازگار بررسی میشوند.
- filter و sort فقط روی ستونهای دارای شاخص مجازند.
- آزمون تکرار/جاافتادگی و عملکرد صفحهٔ عمیق انجام شده است.
منابع و مراجع
- AIP-158: Pagination — Google API Improvement Proposals، 2026-10-19.
- We need tool support for keyset pagination (No Offset) — Use The Index, Luke، 2026-10-19.
- LIMIT and OFFSET — PostgreSQL Documentation، 2026-10-19.
- Pagination (Stripe API) — Stripe Docs، 2026-10-19.
نویسنده
سا
سهیل ابراهیمپور بنیانگذار FutureForge است. روی طراحی محصول، معماری و استقرار نرمافزار سفارشی کار میکند.
یادداشتهای مرتبط
برای انتخاب مسیر فنی پروژهتان کمک میخواهید؟
از مسئله و محدودیتهایتان بگویید؛ اگر بتوانیم همراهیتان کنیم، برای گفتوگو هماهنگ میکنیم.




