Next.js متعدد المستأجرين مع Supabase RLS: دليل الإنتاج
لماذا Supabase RLS للعديد من المستأجرين
لنكن صريحين: عندما يتعلق الأمر بالتعامل مع العديد من المستأجرين في تطبيقات SaaS، لديك خيارات. يمكنك إنشاء قاعدة بيانات منفصلة لكل مستأجر، وهو يبدو مثل الجنة التنظيمية لكن غالٍ جداً وصعب جداً في الإدارة. أو، يمكنك محاولة استخدام مخططات منفصلة، وهي أقل إزعاجاً من الناحية التشغيلية لكنها ليست سهلة عندما يتعلق الأمر بالهجرات. لكن بعد ذلك، هناك المفضل في عالم SaaS—الجداول المشتركة مع التصفية على مستوى الصف. يجعل Supabase هذا النهج سهلاً جداً بفضل أمان مستوى الصف الأصلي في PostgreSQL (RLS).
لماذا يهم ذلك حتى؟ بسيط. يحدث تصفية البيانات الخاصة بك على مستوى قاعدة البيانات. إذا أخطأت في WHERE في مسار Next.js API الخاص بك، فلن تستيقظ في الليل تفكر في انتهاكات البيانات، لأن قاعدة البيانات نفسها هي شبكة الأمان الخاصة بك. وفي الواقع، في هذا العصر، هذا ليس رفاهية—إنها ضرورة.
لكن دعونا لا نخدع أنفسنا. يضيف RLS كثافة في الاستعلامات، ويعقد التصحيح، ويمكن أن يعثر عليك أثناء الهجرات. إذاً، كيف تكدس نهج العديد من المستأجرين المختلفة؟
| النهج | مستوى العزل | التكلفة | التعقيد التشغيلي | أداء الاستعلام |
|---|---|---|---|---|
| قاعدة البيانات لكل مستأجر | كامل | مرتفع (50-200 دولار/مستأجر/شهر) | مرتفع جداً | الأفضل |
| المخطط لكل مستأجر | قوي | متوسط | مرتفع (الهجرات) | جيد |
| الجداول المشتركة + RLS | على مستوى الصف | منخفض | متوسط | جيد (مع تحفظات) |
| التصفية على مستوى التطبيق | لا شيء | الأقل | منخفض | الأفضل |
بالنسبة لمعظم منتجات SaaS التي يقل عدد مستأجريها عن 10000، توفر الجداول المشتركة مع RLS أفضل قيمة مقابل المال. هذا ما نتعمق فيه هنا.

أنماط العمارة: المشترك مقابل المعزول
قبل حتى التفكير في كتابة الكود، يجب عليك اختيار استراتيجية تحديد المستأجر الخاصة بك. في البرية، ستصادف بشكل أساسي نهجين:
العديد من المستأجرين القائمة على النطاق الفرعي
هل سمعت من قبل tenant-slug.yourapp.com? مرحباً بك في أكثر الأنماط شيوعاً لـ B2B SaaS. إنه أنيق واحترافي، ويجعل تحديد المستأجر في البرنامج الوسيط سهلاً جداً.
العديد من المستأجرين القائمة على المسار
هذا واحد أساسي /org/tenant-slug/dashboard. أسهل في الإعداد لأنه لا توجد DNS بدل، وهو يعمل على منصات مثل Vercel بدون نطاقات مخصصة. لكن لنكن صريحين: يبدو وكأنك ترتدي الجوارب مع الصنادل. عادة ما نوصي بالنطاق الفرعي لتطبيقات B2B الإنتاجية والمسار للأدوات الداخلية أو MVPs. التبديل لاحقاً؟ ستلعن نفسك الماضية—تغيير هذه الأنماط ليس سهلاً.
إعداد مخطط المستأجر
إليك نمط المخطط الذي لم يخيب آمالنا في ثلاث عمليات إطلاق إنتاجية مختلفة:
-- جدول المستأجر الأساسي
CREATE TABLE organizations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
slug TEXT UNIQUE NOT NULL,
plan TEXT NOT NULL DEFAULT 'free',
created_at TIMESTAMPTZ DEFAULT now(),
settings JSONB DEFAULT '{}'
);
-- جدول تقاطع العضوية
CREATE TABLE memberships (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
org_id UUID REFERENCES organizations(id) ON DELETE CASCADE,
role TEXT NOT NULL DEFAULT 'member' CHECK (role IN ('owner', 'admin', 'member', 'viewer')),
created_at TIMESTAMPTZ DEFAULT now(),
UNIQUE(user_id, org_id)
);
-- مثال جدول نطاق المستأجر
CREATE TABLE projects (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID REFERENCES organizations(id) ON DELETE CASCADE NOT NULL,
name TEXT NOT NULL,
description TEXT,
created_by UUID REFERENCES auth.users(id),
created_at TIMESTAMPTZ DEFAULT now()
);
-- الفهرس على org_id — ستحتاج هذا في كل جدول نطاق المستأجر
CREATE INDEX idx_projects_org_id ON projects(org_id);
CREATE INDEX idx_memberships_user_id ON memberships(user_id);
CREATE INDEX idx_memberships_org_id ON memberships(org_id);
جدول memberships هو الصمغ الذي يربط كل شيء معاً. ستشير جميع سياسات RLS الخاصة بك إليه وكأنه أحب بني أخ لهم. يمكن للمستخدمين الانضمام إلى عدة منظمات، وأدوارهم تحدد ما يمكنهم وما لا يمكنهم فعله. وإليك قطعة من الحكمة: دائماً—جدياً، دائماً—قم بفهرسة org_id على كل جدول نطاق المستأجر. وإلا، شاهد استعلاماتك تزحف مثل الدبس بمجرد أن تكون تسبح في البيانات. تفاجأنا بهذا عندما أخذ لوحة العميل الخاصة بنا غطسة من 50 ملي ثانية إلى 8 ثوانٍ مع 100000 صف. درس تعلم.
سياسات RLS التي تتسع حقاً
هنا حيث تنحني البرامج التعليمية عادة، تاركة لك عالقاً. يرمون auth.uid() = user_id عليك ويقولون، "حظ سعيد!" لكن RLS متعدد المستأجرين لا يمكن غليه مثل ذلك.
نمط دالة المساعد
لماذا تزدحم كل سياسة بفحوصات العضوية؟ استخدم دالة مساعد بدلاً من ذلك:
-- مساعد: تحقق ما إذا كان المستخدم الحالي عضواً في منظمة
CREATE OR REPLACE FUNCTION public.is_member_of(org UUID)
RETURNS BOOLEAN AS $$
SELECT EXISTS (
SELECT 1 FROM memberships
WHERE user_id = auth.uid()
AND org_id = org
);
$$ LANGUAGE sql SECURITY DEFINER STABLE;
-- مساعد: احصل على دور المستخدم في منظمة
CREATE OR REPLACE FUNCTION public.get_role_in(org UUID)
RETURNS TEXT AS $$
SELECT role FROM memberships
WHERE user_id = auth.uid()
AND org_id = org
LIMIT 1;
$$ LANGUAGE sql SECURITY DEFINER STABLE;
لماذا SECURITY DEFINER؟ لأن الدالة تعمل بامتيازات المنشئ، تخطي RLS على جدول memberships. بدون هذا، تخاطر بالوقوع في حفرة أرنب الاعتماد الدائري حيث RLS على memberships يحطم الفحوصات الأعضاء الأخرى تعتمد عليها.
و STABLE جزء؟ يشير إلى المخطط الاستعلام أن مخرجات الدالة تبقى متسقة للإدخال نفسه أثناء استعلام واحد، مما يتيح بعض فوائد التخزين المؤقت اللطيفة. مغري باستخدام IMMUTABLE؟ لا. العضوية يمكن أن تقلب بين المعاملات.
سياسات جداول نطاق المستأجر
دعونا ننظر إلى بعض السياسات لجدول projects الخاص بنا:
-- تمكين RLS
ALTER TABLE projects ENABLE ROW LEVEL SECURITY;
-- SELECT: يمكن للأعضاء عرض المشاريع في منظماتهم
CREATE POLICY "Members can view org projects"
ON projects FOR SELECT
USING (public.is_member_of(org_id));
-- INSERT: يمكن للمسؤولين والمالكين إنشاء المشاريع
CREATE POLICY "Admins can create projects"
ON projects FOR INSERT
WITH CHECK (
public.get_role_in(org_id) IN ('owner', 'admin')
);
-- UPDATE: يمكن للمسؤولين والمالكين تحديث المشاريع
CREATE POLICY "Admins can update projects"
ON projects FOR UPDATE
USING (public.is_member_of(org_id))
WITH CHECK (
public.get_role_in(org_id) IN ('owner', 'admin')
);
-- DELETE: فقط المالكون يمكنهم حذف المشاريع
CREATE POLICY "Owners can delete projects"
ON projects FOR DELETE
USING (
public.get_role_in(org_id) = 'owner'
);
سياسات جدول العضويات نفسه
هذا واحد محرج. جدول memberships يحصل على RLS الخاص به، لكنها لا تستطيع استخدام الدوال المساعدة لأنها، بدورها، تستعلم memberships—أشر إلى كوابيس الاعتماد الدائري:
ALTER TABLE memberships ENABLE ROW LEVEL SECURITY;
-- يمكن للمستخدمين رؤية العضويات في المنظمات التي ينتمون إليها
CREATE POLICY "Users can view org memberships"
ON memberships FOR SELECT
USING (
org_id IN (
SELECT org_id FROM memberships WHERE user_id = auth.uid()
)
);
-- فقط المالكون يمكنهم إضافة أعضاء
CREATE POLICY "Owners can add members"
ON memberships FOR INSERT
WITH CHECK (
org_id IN (
SELECT org_id FROM memberships
WHERE user_id = auth.uid() AND role = 'owner'
)
);
نعم، هناك استعلام فرعي على نفس الجدول. ونعم، PostgreSQL يسدد الضربات. الاستعلام الفرعي يفحص عضويتك الخاصة، دون تأثر بالسياسة التي يتم تعريفها لأن RLS تلتف فقط حول الاستعلام الخارجي. لكن اختبر هذا—جدياً، أنت لا تريد إيجاد خلل في الإنتاج.

Next.js Middleware لتحديد المستأجر
مع Next.js 15 والموجه التطبيق اللامع، يعمل البرنامج الوسيط في الحافة يشبه صاحب المنزل المثالي لتحديد المستأجر. إليك نمطنا الموثوق به للإعدادات القائمة على النطاق الفرعي:
// middleware.ts
import { createServerClient } from '@supabase/ssr';
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
const PUBLIC_ROUTES = ['/login', '/signup', '/invite'];
export async function middleware(request: NextRequest) {
const hostname = request.headers.get('host') || '';
const currentHost = hostname.split('.')[0];
// تخطي النطاق الرئيسي وlocalhost
const isMainDomain = currentHost === 'app' || currentHost === 'www' || currentHost === 'localhost:3000';
let response = NextResponse.next({
request: { headers: request.headers },
});
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
return request.cookies.getAll();
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value, options }) => {
request.cookies.set(name, value);
response.cookies.set(name, value, options);
});
},
},
}
);
const { data: { user } } = await supabase.auth.getUser();
if (!isMainDomain) {
response.headers.set('x-tenant-slug', currentHost);
if (!user && !PUBLIC_ROUTES.some(r => request.nextUrl.pathname.startsWith(r))) {
return NextResponse.redirect(new URL('/login', request.url));
}
}
return response;
}
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico|api/webhooks).*)'],
};
رأس x-tenant-slug هو ذهب نقي. استخدمه لترك المكونات الخادم والمسارات API تعرف أي مستأجر يتعاملون معه. إذا كنت تتعاون معنا في مشروع Next.js، فإن إعداد هذا هو أولويتنا في اليوم الأول.
سير المصادقة في تطبيقات متعددة المستأجرين
Supabase Auth يلعبها حيادياً في لعبة العديد من المستأجرين. يوجد المستخدمون في كرة عالمية—العلاقات بالمستأجرين هي لغزك لحل. هنا خطتنا الاستراتيجية:
- المستخدم يسجل: إنشاء مستخدم auth، بناء منظمة، وإنشاء عضوية مع دور 'owner'.
- المستخدم مدعو: يرسم الإداري دعوة معلقة، ينضم مستخدم جديد عبر رابط الدعوة، وفويلا—تظهر عضوية بالدور المحدد.
- المستخدم يسجل الدخول: استخراج المستأجر من النطاق الفرعي، تأكيد العضوية، مرافقتهم إلى لوحة التحكم الخاصة بهم.
// app/api/auth/signup/route.ts
import { createClient } from '@/lib/supabase/server';
import { NextResponse } from 'next/server';
export async function POST(request: Request) {
const { email, password, orgName, orgSlug } = await request.json();
const supabase = await createClient();
// سجل المستخدم
const { data: authData, error: authError } = await supabase.auth.signUp({
email,
password,
});
if (authError) return NextResponse.json({ error: authError.message }, { status: 400 });
// استخدم عميل دور الخدمة لإنشاء org (تخطي RLS)
const adminClient = createAdminClient();
const { data: org, error: orgError } = await adminClient
.from('organizations')
.insert({ name: orgName, slug: orgSlug })
.select()
.single();
if (orgError) return NextResponse.json({ error: orgError.message }, { status: 400 });
// إنشاء عضوية الملكية
await adminClient
.from('memberships')
.insert({
user_id: authData.user!.id,
org_id: org.id,
role: 'owner',
});
return NextResponse.json({ org });
}
لاحظ أننا نعتمد على عميل دور الخدمة أثناء التسجيل. المستخدم لم يحصل على أي عضويات حتى الآن، لذا سيترك RLS في الحضيض لإنشاء المنظمة. إنها مشكلة واحدة من تلك الكلاسيكيات للتمهيد—سيكون مفتاح دور الخدمة الخاص بك هو عصا سحرك.
وأنا لا أستطيع التأكيد بما فيه الكفاية: لا تكشف أبداً مفتاح دور الخدمة الخاص بك للعميل. إنه بحتة لكود الجانب الخادم.
مكونات الخادم و RLS: مشكلة SSR
مكونات الخادم في Next.js 15 مرتبطة بالخادم، مما يزيد من لعبة الأمان. لكن هناك نقرة عند استخدام Supabase RLS: يجب عليك توفير جلسة المستخدم لعميل Supabase لكي تعرف سياسات RLS من في الطاولة.
// lib/supabase/server.ts
import { createServerClient } from '@supabase/ssr';
import { cookies } from 'next/headers';
export async function createClient() {
const cookieStore = await cookies();
return createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
return cookieStore.getAll();
},
setAll(cookiesToSet) {
try {
cookiesToSet.forEach(({ name, value, options }) =>
cookieStore.set(name, value, options)
);
} catch {
// هذا يمكن أن يفشل في مكونات الخادم (للقراءة فقط)
// البرنامج الوسيط يتعامل مع تحديث ملفات تعريف الارتباط
}
},
},
}
);
}
// app/[orgSlug]/projects/page.tsx
import { createClient } from '@/lib/supabase/server';
import { headers } from 'next/headers';
export default async function ProjectsPage() {
const supabase = await createClient();
const headersList = await headers();
const tenantSlug = headersList.get('x-tenant-slug');
// احصل على معرف org من slug
const { data: org } = await supabase
.from('organizations')
.select('id')
.eq('slug', tenantSlug)
.single();
if (!org) return <div>Organization not found</div>;
// RLS تصفي تلقائياً — تعود فقط المشاريع
// حيث للمستخدم الحالي عضوية
const { data: projects } = await supabase
.from('projects')
.select('*')
.eq('org_id', org.id)
.order('created_at', { ascending: false });
return (
<div>
{projects?.map(project => (
<ProjectCard key={project.id} project={project} />
))}
</div>
);
}
هنا الحيلة: حتى لو اختلق شخص ما org_id في الطلب، لن تتحرك RLS. إنها تحجب الوصول إلى المشاريع إلا إذا كان المستخدم عضواً. من الناحية الفنية، .eq('org_id', org.id) زائد للأمان—RLS يتعامل مع ذلك—لكنها جيدة للأداء والوضوح.
تحسين الأداء والمزالق الشائعة
مشكلة N+1 استعلام RLS
كل فحص سياسة RLS يدور استعلام فرعي. الربط في فحص سياسة 10-row عندما تنظر إلى 100 صف يعني 100 جولة من البحث عن العضوية. لحسن الحظ، PostgreSQL ذكية بما يكفي للتخزين المؤقت—لكن هناك كثافة.
إصلاح: استخدم STABLE على الدوال المساعدة (كما أوضحنا). أيضاً، فكر في إلغاء تطبيع org_id في مطالبات JWT:
-- خطاف JWT مخصص (لوحة معلومات Supabase > Auth > Hooks)
CREATE OR REPLACE FUNCTION public.custom_access_token_hook(event JSONB)
RETURNS JSONB AS $$
DECLARE
org_ids UUID[];
BEGIN
SELECT array_agg(org_id) INTO org_ids
FROM memberships
WHERE user_id = (event->>'user_id')::UUID;
event := jsonb_set(
event,
'{claims,org_ids}',
to_jsonb(org_ids)
);
RETURN event;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
ثم تصبح سياستك RLS:
CREATE POLICY "Members can view"
ON projects FOR SELECT
USING (
org_id = ANY(
(SELECT array(SELECT jsonb_array_elements_text(
auth.jwt()->'org_ids'
))::UUID[])
)
);
هذا يسحق نظر جدول العضوية تماماً. معرفات org تأتي مباشرة من JWT. تحذير: مطالبات JWT ملمسة عند تسجيل الدخول. غير عضوية شخص، وسيحتاجون إلى إعادة مصادقة لمزامنة المطالبات. عادة، هذا قابل للتعامل تماماً—فقط احفظه في وثائقك.
تجميع الاتصالات
يوزع Supabase تجميع الاتصالات من خلال PgBouncer. إذا كنت ستذهب مباشرة مع Next.js على Vercel، تذكر: عنوان URL للمجمع لمسارات API ومكونات الخادم.
# للعمليات العادية (مجمعة)
DATABASE_URL=postgres://user:pass@db.project.supabase.co:6543/postgres
# للهجرات فقط (مباشرة)
DIRECT_URL=postgres://user:pass@db.project.supabase.co:5432/postgres
أي شخص على Supabase Pro مقابل 25 دولار شهرياً يحصل على 200 اتصال متزامن عبر المجمع. بالنسبة لمعظم تطبيقات الويب التي تقل عن 1000 مستخدم متزامن، فهو أكثر من كافٍ.
الفهارس التي تحتاجها بالفعل
إليك مجموعة الفهرس الغاشمة لإعداد متعدد المستأجرين:
-- في كل جدول نطاق المستأجر
CREATE INDEX idx_{table}_org_id ON {table}(org_id);
-- الفهارس المركبة للاستعلامات الشائعة
CREATE INDEX idx_projects_org_created ON projects(org_id, created_at DESC);
-- العضويات — تم الاستعلام بشكل كبير بواسطة RLS
CREATE INDEX idx_memberships_user_org ON memberships(user_id, org_id);
CREATE INDEX idx_memberships_org_role ON memberships(org_id, role);
EXPLAIN ANALYZE—أفضل صديق للمطور. انظر كيف تستقيم استعلاماتك مع RLS على متن الطائرة. قد تحصل على صحوة فظة على ما يقرره المخطط بدون الفهارس الصحيحة.
اختبار سياسات RLS
الجميع يتخطون هذا، مع ذلك إنه شبكة الأمان الأفضل ضد تسرب البيانات. نختبر سياسات RLS مباشرة في SQL:
-- اختبر كمستخدم معين
SET request.jwt.claims = '{"sub": "user-uuid-here", "role": "authenticated"}';
SET role = 'authenticated';
-- هذا يجب أن يعيد فقط المشاريع التي للمستخدم الوصول إليها
SELECT * FROM projects;
-- هذا يجب أن يفشل (المستخدم ليس عضواً في منظمة أخرى)
INSERT INTO projects (org_id, name) VALUES ('other-org-uuid', 'Sneaky Project');
-- إعادة تعيين
RESET role;
وودعنا لا ننسى pgTAP للسياسات الحرجة:
BEGIN;
SELECT plan(3);
-- إعداد سياق الاختبار كمستخدم A (عضو في org 1)
SET LOCAL request.jwt.claims = '{"sub": "user-a-uuid"}';
SET LOCAL role = 'authenticated';
SELECT is(
(SELECT count(*) FROM projects WHERE org_id = 'org-1-uuid')::INTEGER,
5,
'User A sees 5 projects in their org'
);
SELECT is(
(SELECT count(*) FROM projects WHERE org_id = 'org-2-uuid')::INTEGER,
0,
'User A sees 0 projects in other org'
);
SELECT throws_ok(
$$INSERT INTO projects (org_id, name) VALUES ('org-2-uuid', 'Hack')$$,
'new row violates row-level security policy',
'User A cannot insert into other org'
);
SELECT * FROM finish();
ROLLBACK;
قم بتشغيل هذه في CI. كل هجرة تعديل سياسات RLS يجب أن تمر المجموعة الكاملة للاختبار من خلال تمرين قوي.
قائمة التحقق من نشر الإنتاج
مستعد للشحن؟ أمسك نفسك بهذا:
- RLS مفعل على كل جدول يحمل بيانات المستأجر
- مفتاح دور الخدمة محفوظ في الجانب الخادم، بعيداً عن العميل
-
org_idبشكل صحيح مفهرس على جميع جداول نطاق المستأجر - دوال المساعد للعضوية مكرسة كـ
SECURITY DEFINERوSTABLE - مطالبات JWT المخصصة مقفلة ومحملة (إذا كان على مسار JWT)
- تجميع الاتصالات مشفي لنشر السحابة؟
- سياسات RLS طازجة من ضمان الجودة مع pgTAP أو نظيره
- كرنك
EXPLAIN ANALYZEعلى الاستعلامات الحاسمة مع تشغيل RLS - تدفق الدعوة/التسجيل لا ينقص أي تمهيد العضوية
- أي تحديد معدل على نقاط نهاية المصادقة؟ Supabase يقدم خيارات مطبوخة
- قلب المفتاح على RLS لجداول مخطط
authفي لوحة معلومات Supabase (غالباً ألغام) - المراقبة المضمنة لأي استعلامات بطيئة (لوحة معلومات Supabase > Database > Query Performance)
تطلق منتجاً متعدد المستأجرين وتريد شخصاً مر عبر هذه المياه من قبل؟ حلول تطوير CMS بدون رأس أو دردشة سريعة من خلال صفحة الاتصال قد تكون بالضبط ما تحتاجه.
الأسئلة الشائعة
هل يمكنني استخدام Supabase RLS للتطبيقات التي تحتوي على آلاف المستأجرين؟
نعم تماماً. لقد قمنا بتجريب RLS جدول مشترك مع 5000+ مستأجر وملايين الصفوف بدون كسر العرق. الصلصة السرية؟ الفهرسة الصحيحة على أعمدة org_id ودوال مساعدة STABLE. بعد النظر في 50000+ مستأجر أو الكماليات مليار صف؟ الغوص في تقسيم الجداول حسب org_id أو الغزل مع إعداد مخطط لكل مستأجر.
كيف أتعامل مع تبديل المستأجر عندما ينتمي مستخدم إلى عدة منظمات؟ احفظ المنظمة النشطة في ملف تعريف أرتباط أو عنوان URL (نطاق فرعي). تبديل orgs؟ عدل النطاق الفرعي/ملف تعريف الارتباط واجلب جديد. لا تضع المنظمة النشطة في JWT—فهي تتطلب رسجول لتغيير. ملف تعريف الارتباط يمكن لوسيطك أن يخطئ فيه هو الطريق.
ماذا يحدث إذا نسيت تمكين RLS على جدول؟
كل مستخدم موثق يمكنه الاستفادة من كل صف. هذا موقف PostgreSQL الافتراضي—لا قيود على مستوى الصف على الجداول بدون RLS. لوحة معلومات Supabase تعلم جداول مفقودة RLS، لكن دمج هذا في CI مع استعلامات على pg_tables و pg_policies يساعد أيضاً.
هل يجب أن أستخدم مفتاح دور الخدمة الخاص بـ Supabase أو أطهو دور PostgreSQL مخصص لواجبات الإدارة؟ معظم الوقت، مفتاح دور الخدمة يكفي. إنه يتجنب RLS تماماً، لذا فهو أعلى سري لاستخدام الخادم فقط. هل تحتاج إلى حكم حبيبي (مثل دور "admin" يختبئ في جميع orgs لكن خجول من الحذف)؟ هذا إقليم PostgreSQL المخصص—متقدم وعادة خارج رادارك حتى الأدوات الداخلية المعقدة تطلبها.
كيف أقوم بتشغيل هجرات قاعدة البيانات دون الزحف على سياسات RLS؟
Supabase CLI (supabase db push أو supabase migration) إلى جانب عنوان URL قاعدة البيانات المباشرة (تخطي مجمعة) لديك ظهرك. أدرج تعديلات سياسة RLS في نفس الهجرة كالتعديلات المخطط. اختبر صب الهجرات ضد مشروع التدريج—Supabase يسمح لك بالدوران في فروع معاينة على Pro لهذا النوع بالضبط من الشيء.
هل يمكن لسياسات RLS الوصول إلى البيانات من واجهات برمجية أخرى أو خدمات؟
لا. سياسات RLS تجلس بشكل مريح في SQL، تقيمها PostgreSQL. تهتز بالتحقق على البيانات الخارجية (مثل خدمة علم الميزة)؟ اسمنت البيانات في جدول قاعدة البيانات، ثم مرجع في سياستك. نمط نموذجي هو مزامنة حالات الاشتراك من Stripe إلى عمود organizations.plan.
ما هي ضريبة الأداء لـ RLS مقابل التصفية على مستوى التطبيق؟ فوق في معايارنا Supabase Pro (2 vCPU، 8GB RAM)، تحقن RLS ملح 1-3ms إضافية لكل استعلام لسياسات الفحص الأساسية العضوية مع الفهارس الصحيحة. اذهب جامحة مع تعقيد السياسة أو الربط وقد تضيف 5-15ms. خدعة مطالبات JWT (تخزين org_ids في الرمز) تقطع تحتها 1ms لأنه لا يوجد رقصة استعلام فرعية. بالنسبة لتطبيقات الويب النموذجية، تلك نقطة من الكمون لا يذكر.
كيف يعمل هذا مع اشتراكات Supabase Realtime؟
Supabase Realtime تلعب بقواعد كتاب RLS. اضبط على تغييرات الجدول واحصل على الأحداث فقط للصفوف التي تتمتع بأهلية الرؤية وفقاً لـ RLS. هذا يتعرض من الصندوق بدون نكات إضافية. فقط تأكد من أن عميل Supabase من جهة العميل لديه جلسة المستخدم، وهو @supabase/ssr يتعامل معه بسلاسة.