ACID Properties¶
Understanding ACID properties in Zenoo RPC transactions.
Overview¶
ACID properties ensure reliable transaction processing: - Atomicity: All operations succeed or all fail - Consistency: Database remains in valid state - Isolation: Concurrent transactions don't interfere - Durability: Committed changes persist
Atomicity¶
All-or-Nothing Operations¶
async def atomic_operations_example():
"""Demonstrate atomic operations."""
async with ZenooClient("localhost", port=8069) as client:
await client.login("demo", "admin", "admin")
try:
async with client.transaction() as tx:
# All these operations will succeed or all will fail
partner = await client.model("res.partner").create({
"name": "ACME Corp",
"email": "contact@acme.com"
})
product = await client.model("product.product").create({
"name": "ACME Widget",
"list_price": 100.0
})
order = await client.model("sale.order").create({
"partner_id": partner.id,
"order_line": [(0, 0, {
"product_id": product.id,
"product_uom_qty": 5
})]
})
# If any operation fails, all are rolled back
if order.amount_total < 0: # Business rule violation
raise ValueError("Invalid order total")
print(f"Order created successfully: {order.id}")
except Exception as e:
print(f"Transaction failed, all operations rolled back: {e}")
Partial Failure Handling¶
async def handle_partial_failures():
"""Handle partial failures in batch operations."""
async with ZenooClient("localhost", port=8069) as client:
await client.login("demo", "admin", "admin")
partners_data = [
{"name": "Valid Partner 1", "email": "valid1@example.com"},
{"name": "", "email": "invalid@example.com"}, # Invalid - empty name
{"name": "Valid Partner 2", "email": "valid2@example.com"}
]
# Atomic approach - all or nothing
try:
async with client.transaction() as tx:
created_partners = []
for partner_data in partners_data:
# Validate before creating
if not partner_data.get("name"):
raise ValueError(f"Invalid partner data: {partner_data}")
partner = await client.model("res.partner").create(partner_data)
created_partners.append(partner)
print(f"All {len(created_partners)} partners created successfully")
except Exception as e:
print(f"Batch creation failed, no partners created: {e}")
Consistency¶
Data Integrity Constraints¶
async def maintain_data_consistency():
"""Demonstrate data consistency maintenance."""
async with ZenooClient("localhost", port=8069) as client:
await client.login("demo", "admin", "admin")
async with client.transaction() as tx:
# Create customer
customer = await client.model("res.partner").create({
"name": "Consistent Customer",
"customer_rank": 1
})
# Create product
product = await client.model("product.product").create({
"name": "Consistent Product",
"list_price": 50.0,
"type": "product"
})
# Create order with consistent data
order = await client.model("sale.order").create({
"partner_id": customer.id,
"state": "draft"
})
# Add order line
order_line = await client.model("sale.order.line").create({
"order_id": order.id,
"product_id": product.id,
"product_uom_qty": 2,
"price_unit": product.list_price
})
# Verify consistency
await order.action_confirm() # This will validate business rules
print(f"Consistent order created: {order.name}")
async def handle_consistency_violations():
"""Handle consistency constraint violations."""
async with ZenooClient("localhost", port=8069) as client:
await client.login("demo", "admin", "admin")
try:
async with client.transaction() as tx:
# Attempt to create order with non-existent customer
order = await client.model("sale.order").create({
"partner_id": 99999, # Non-existent customer
"state": "draft"
})
except Exception as e:
print(f"Consistency violation prevented: {e}")
Isolation¶
Transaction Isolation Levels¶
import asyncio
async def demonstrate_isolation():
"""Demonstrate transaction isolation."""
async def transaction_1():
"""First concurrent transaction."""
async with ZenooClient("localhost", port=8069) as client:
await client.login("demo", "admin", "admin")
async with client.transaction() as tx:
print("Transaction 1: Starting")
# Read partner
partner = await client.model("res.partner").filter(id=1).first()
original_name = partner.name
print(f"Transaction 1: Read partner name: {original_name}")
# Simulate processing time
await asyncio.sleep(2)
# Update partner
await partner.update({"name": f"{original_name} - Updated by TX1"})
print("Transaction 1: Updated partner")
# Simulate more processing
await asyncio.sleep(1)
print("Transaction 1: Committing")
async def transaction_2():
"""Second concurrent transaction."""
async with ZenooClient("localhost", port=8069) as client:
await client.login("demo", "admin", "admin")
# Start slightly after transaction 1
await asyncio.sleep(1)
async with client.transaction() as tx:
print("Transaction 2: Starting")
# Read same partner
partner = await client.model("res.partner").filter(id=1).first()
print(f"Transaction 2: Read partner name: {partner.name}")
# Update partner
await partner.update({"name": f"{partner.name} - Updated by TX2"})
print("Transaction 2: Updated partner")
print("Transaction 2: Committing")
# Run transactions concurrently
await asyncio.gather(transaction_1(), transaction_2())
Avoiding Race Conditions¶
async def avoid_race_conditions():
"""Demonstrate race condition avoidance."""
async def increment_counter_safely(counter_id: int):
"""Safely increment a counter value."""
async with ZenooClient("localhost", port=8069) as client:
await client.login("demo", "admin", "admin")
async with client.transaction() as tx:
# Use SELECT FOR UPDATE equivalent
counter = await client.model("res.partner").filter(
id=counter_id
).first()
if counter:
# Read current value
current_value = getattr(counter, 'ref', '0')
try:
numeric_value = int(current_value) if current_value else 0
except ValueError:
numeric_value = 0
# Increment
new_value = numeric_value + 1
# Update atomically
await counter.update({"ref": str(new_value)})
print(f"Counter incremented to: {new_value}")
return new_value
# Run multiple concurrent increments
tasks = [increment_counter_safely(1) for _ in range(5)]
results = await asyncio.gather(*tasks)
print(f"Final counter values: {results}")
Durability¶
Ensuring Persistent Changes¶
async def ensure_durability():
"""Demonstrate durability of committed transactions."""
async with ZenooClient("localhost", port=8069) as client:
await client.login("demo", "admin", "admin")
# Create important data that must persist
async with client.transaction() as tx:
critical_partner = await client.model("res.partner").create({
"name": "Critical Business Partner",
"email": "critical@business.com",
"customer_rank": 1,
"supplier_rank": 1
})
# Create related records
contract = await client.model("res.partner").create({
"name": "Service Contract",
"parent_id": critical_partner.id,
"is_company": False
})
print(f"Critical data created: Partner {critical_partner.id}, Contract {contract.id}")
# Transaction commits here - data is now durable
# Verify data persists after transaction
await asyncio.sleep(1) # Simulate system activity
# Read back the data
persisted_partner = await client.model("res.partner").filter(
id=critical_partner.id
).first()
if persisted_partner:
print(f"Data persisted successfully: {persisted_partner.name}")
else:
print("ERROR: Data was not persisted!")
async def handle_system_failures():
"""Demonstrate handling of system failures."""
async with ZenooClient("localhost", port=8069) as client:
await client.login("demo", "admin", "admin")
try:
async with client.transaction() as tx:
# Create data
partner = await client.model("res.partner").create({
"name": "Pre-failure Partner"
})
# Simulate system failure before commit
# In real scenarios, this could be network failure, server crash, etc.
raise SystemError("Simulated system failure")
except SystemError as e:
print(f"System failure occurred: {e}")
print("Transaction was not committed - no data persisted")
# Verify no partial data exists
failed_partners = await client.model("res.partner").filter(
name="Pre-failure Partner"
).all()
print(f"Partners found after failure: {len(failed_partners)}")
ACID Compliance Best Practices¶
Transaction Design Patterns¶
class ACIDCompliantService:
"""Service class demonstrating ACID-compliant operations."""
def __init__(self, client: ZenooClient):
self.client = client
async def create_customer_with_order(self, customer_data: dict, order_data: dict) -> dict:
"""Create customer and initial order atomically."""
async with self.client.transaction() as tx:
# Validate input data (Consistency)
self._validate_customer_data(customer_data)
self._validate_order_data(order_data)
# Create customer (Atomicity)
customer = await self.client.model("res.partner").create({
**customer_data,
"customer_rank": 1
})
# Create order (Atomicity)
order = await self.client.model("sale.order").create({
**order_data,
"partner_id": customer.id,
"state": "draft"
})
# Verify business rules (Consistency)
if order.amount_total < 0:
raise ValueError("Order total cannot be negative")
# All operations succeed together (Atomicity)
# Changes are isolated from other transactions (Isolation)
# Changes will persist after commit (Durability)
return {
"customer_id": customer.id,
"order_id": order.id,
"status": "success"
}
def _validate_customer_data(self, data: dict):
"""Validate customer data for consistency."""
if not data.get("name"):
raise ValueError("Customer name is required")
if data.get("email") and "@" not in data["email"]:
raise ValueError("Invalid email format")
def _validate_order_data(self, data: dict):
"""Validate order data for consistency."""
if not data.get("order_line"):
raise ValueError("Order must have at least one line")
async def transfer_customer_data(self, from_customer_id: int, to_customer_id: int) -> dict:
"""Transfer data between customers atomically."""
async with self.client.transaction() as tx:
# Read source customer (Isolation)
source = await self.client.model("res.partner").filter(
id=from_customer_id
).first()
if not source:
raise ValueError(f"Source customer {from_customer_id} not found")
# Read target customer (Isolation)
target = await self.client.model("res.partner").filter(
id=to_customer_id
).first()
if not target:
raise ValueError(f"Target customer {to_customer_id} not found")
# Transfer orders atomically
orders = await self.client.model("sale.order").filter(
partner_id=from_customer_id,
state__in=["draft", "sent"]
).all()
transferred_count = 0
for order in orders:
await order.update({"partner_id": to_customer_id})
transferred_count += 1
# Update customer status (Consistency)
await source.update({"active": False})
# All changes are atomic and will be durable
return {
"transferred_orders": transferred_count,
"source_deactivated": True,
"status": "success"
}
# Usage
async def use_acid_service():
"""Demonstrate ACID-compliant service usage."""
async with ZenooClient("localhost", port=8069) as client:
await client.login("demo", "admin", "admin")
service = ACIDCompliantService(client)
try:
# Create customer with order (ACID compliant)
result = await service.create_customer_with_order(
customer_data={
"name": "ACID Test Customer",
"email": "acid@test.com"
},
order_data={
"order_line": [(0, 0, {
"product_id": 1,
"product_uom_qty": 1
})]
}
)
print(f"ACID operation completed: {result}")
except Exception as e:
print(f"ACID operation failed (all changes rolled back): {e}")
Best Practices¶
- Keep Transactions Short: Minimize transaction duration to reduce lock contention
- Validate Early: Perform validation before starting expensive operations
- Handle Conflicts: Implement proper handling for isolation conflicts
- Monitor Performance: Track transaction performance and deadlock rates
- Test Concurrency: Test your application under concurrent load
Related¶
- Transaction Manager - Transaction management
- Transaction Context - Context usage
- Transaction Exceptions - Error handling