Django / الصحة ودليل الإنتاج / الدرس 13
المعاملات والكتابة المتزامنة
احم انتقال العمل كاملا بقيود وكتابة محروسة وآثار بعد الالتزام صريحة.
في هذه الصفحة
أهداف الدرس
- المعاملة تحمي حد قاعدة
- حراسة خصم السعة الفعلي
- معالجة الفشل بحد نظيف
المعاملة تحمي حد قاعدة
تستخدم Django الالتزام التلقائي عادة، وتجمع atomic عمل القاعدة بمعاملات ونقاط حفظ مدعومة. لا تعني الذرية سلامة كل قرار متزامن أو عكس كل أثر خارجي. يحتاج خصم المقاعد وإنشاء الحجز حدا متماسكا والبريد والدفع تنسيقا منفصلا. قصر المعاملة وتجنب انتظار مستخدم وشبكة وصرح بالثابت قبل العزل والقفل والإعادة. ولا تشمل معاملة القاعدة أثر رسالة أو مزود دفع خارجي تلقائيا، لذلك يجب أن يبين التصميم كيف تصالح هذه الآثار إذا فشل جزء من التدفق.
ليست القراءة السابقة حجزا
قد يقرأ طلبان السعة نفسها؛ فاجعل الكتابة تفرضها بشرط أو قيد أو قفل. تنفذ F الحساب في القاعدة لا قيمة Python قديمة ولا تعوض كل ثابت متعدد الصفوف. تحتاج select_for_update محركا ومعاملة داعمين ولا تعادل SQLite قفل PostgreSQL. لا تحول تجربة متسلسلة لإثبات كل التداخلات. وحتى الحساب الذي تنفذه F في القاعدة لا يحل وحده علاقة بين عدة صفوف، فقد تحتاج تلك العلاقة إلى قيد أو بروتوكول قفل ومعاملة أوضح.
حراسة خصم السعة الفعلي
ينشئ البرنامج ثلاثة مقاعد ويخصم اثنين بشرط الحالي ويبين العدد المتأثر القبول. يرفض الثاني لبقاء واحد. الكمية موجبة ثابتة ويجب تحققها وتفويضها للعامة. يحتاج الحجز الحقيقي معرف طلب ومعاملة صفوف مرتبطة لمنع التكرار والمجهول. تثبت العينة الحساب والشرط لا معيار تنازع إنتاجي. ويجب التحقق من موجبية كمية الطلب قبل هذا المساعد، لأن تطبيق الحساب على كمية خارج عقده قد يغير معنى العملية بدلا من رفضها.
from django.conf import settings
settings.configure(
SECRET_KEY="synthetic-course-key",
USE_I18N=False, USE_TZ=True,
INSTALLED_APPS=[],
DATABASES={"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:"}},
DEFAULT_AUTO_FIELD="django.db.models.AutoField",
ALLOWED_HOSTS=["testserver"],
)
import django
django.setup()
from django.db import connection, models, transaction
from django.db.models import F
class Course(models.Model):
seats = models.PositiveIntegerField()
class Meta:
app_label = "lab"
with connection.schema_editor() as editor:
editor.create_model(Course)
course = Course.objects.create(seats=3)
def reserve(quantity):
with transaction.atomic():
return Course.objects.filter(pk=course.pk, seats__gte=quantity).update(seats=F("seats") - quantity)
print(reserve(2))
print(reserve(2))
print(Course.objects.get(pk=course.pk).seats)
1
0
1
يميز عدد الصفوف القبول من نقص السعة بقاعدة ذاكرية معزولة.
نشر الآثار بعد الالتزام الناجح
يسجل الثاني on_commit فتعمل بعد النجاح وتسقط مع التراجع. يفيد لإشعار بعد وجود البيانات ولا يضمن التسليم الدائم؛ قد يتعطل العمل بعد الالتزام وقبل الأثر، فتحتاج outbox أو بروتوكولا. لا يعكس فشل الرد المعاملة المكتملة. افصل اختبار التسجيل والتنفيذ والتسليم. وقد يتعطل العامل بعد الالتزام وقبل وصول الإشعار، ولهذا لا يكفي نجاح تسجيل callback إذا كان العمل يحتاج إلى نقل دائم قابل للمصالحة.
from django.conf import settings
settings.configure(
SECRET_KEY="synthetic-course-key",
USE_I18N=False, USE_TZ=True,
INSTALLED_APPS=[],
DATABASES={"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:"}},
DEFAULT_AUTO_FIELD="django.db.models.AutoField",
ALLOWED_HOSTS=["testserver"],
)
import django
django.setup()
from django.db import transaction
events = []
with transaction.atomic():
transaction.on_commit(lambda: events.append("committed"))
print(events)
print(events)
try:
with transaction.atomic():
transaction.on_commit(lambda: events.append("rolled-back"))
raise ValueError("Synthetic rollback")
except ValueError:
pass
print(events)
[]
['committed']
['committed']
ينفذ الالتزام الأول ويسقط التراجع الثاني دون بريد أو طابور أو شبكة.
معالجة الفشل بحد نظيف
التقط خطأ القاعدة خارج atomic المتضررة ثم قرر إعادة العملية كاملة بحد. قد يفسد تكرار آخر تعليمة السابق ويكرر الخارجي المجهول. ليست نقاط الحفظ التزاما مستقلا، ولـATOMIC_REQUESTS نطاق لا يضم الوسيط وكل توليد التدفق تلقائيا. راجع العمر والمحرك ووضح التراجع ومنع التكرار والاستعادة. ويجب أن تبدأ الإعادة من حالة نظيفة وتعيد قراءة ما تحتاجه، لأن مزج قراءات محاولة سابقة بآخر أمر جديد قد يكسر الثابت رغم وجود atomic.
الأخطاء الشائعة وإصلاحها
تكتب حجوزات فوق بعضها
- لماذا يحدث؟
- حُفظت قراءة قديمة دون حراسة.
- كيف تصلحه؟
- افرض السعة بشرط أو قفل مناسب.
- كيف تتجنب تكراره؟
- اختبر الثابت والمحرك.
يرسل بريد لسجل متراجع
- لماذا يحدث؟
- سبق الأثر الالتزام.
- كيف تصلحه؟
- نسق النشر والتسليم.
- كيف تتجنب تكراره؟
- افصل اختبار التراجع والإشعار.
تفشل استعلامات بعد التقاط الخطأ
- لماذا يحدث؟
- استمر العمل داخل معاملة متضررة.
- كيف تصلحه؟
- التقط خارجها وأعد نظيفا عند الملاءمة.
- كيف تتجنب تكراره؟
- اختبر الفشل معزولا.
اختبر فهمك
1. هل تضمن on_commit وصول الرسالة مرة واحدة؟
اعرض الحل المشروح
لا؛ لا تضم تنفيذ الرد والتسليم البعيد لمعاملة القاعدة. استخدم بروتوكولا دائما عند الحاجة وصمم معالجة التكرار.
2. لماذا تعاد المحاولة كوحدة كاملة مؤهلة؟
اعرض الحل المشروح
قد تتقادم القراءات والكتابات فيخلط آخر أمر المحاولات. ابدأ نظيفا وأعد القراءة واحفظ هوية العملية وحد الإعادة للفشل المصنف.
أهم ما تعلمته
- الذرية أضيق من صحة العمل الموزع.
- احرس الكتابة بالحالي.
- ردود الالتزام ليست نقلا دائما.