Back to Documentation

Multi-Model Transactions

ACID transactions spanning relational, document, graph, and time-series data

Overview

entropyDB is the first database to support ACID transactions across multiple data models in a single transaction. Update relational tables, modify JSON documents, traverse graphs, and insert time-series data—all with full consistency guarantees.

Why Multi-Model Transactions?

  • Maintain consistency across different data representations
  • Eliminate complex application-level coordination
  • Simplify architecture—one database for all models
  • Full rollback on failure across all models

Basic Multi-Model Transaction

BEGIN;

-- Relational: Update user account
UPDATE users 
SET balance = balance - 99.99 
WHERE id = 123;

-- Document: Create order document
INSERT INTO orders (user_id, data)
VALUES (123, '{
  "items": [{"product": "Laptop", "price": 99.99}],
  "status": "pending",
  "timestamp": "2024-03-15T10:30:00Z"
}'::jsonb);

-- Time-Series: Log event
INSERT INTO events (user_id, event_type, timestamp, metrics)
VALUES (123, 'purchase', NOW(), '{"amount": 99.99}'::jsonb);

-- Graph: Create relationship
INSERT INTO user_product_graph (user_id, product_id, relationship)
VALUES (123, 456, 'purchased');

COMMIT;  -- All or nothing!

Real-World Examples

E-Commerce Order Processing

BEGIN;

-- 1. Relational: Update inventory
UPDATE inventory 
SET quantity = quantity - 1 
WHERE product_id = 789 AND quantity > 0;

-- 2. Document: Store order details
INSERT INTO orders (data) VALUES ('{
  "order_id": "ORD-2024-001",
  "customer": {"id": 123, "email": "user@example.com"},
  "items": [{"product_id": 789, "quantity": 1, "price": 299.99}],
  "shipping": {"address": "123 Main St", "method": "express"},
  "payment": {"method": "credit_card", "status": "pending"}
}'::jsonb);

-- 3. Graph: Create purchase relationship
INSERT INTO user_purchases (user_id, product_id, purchased_at)
VALUES (123, 789, NOW());

-- 4. Time-Series: Log order event
INSERT INTO order_events (order_id, event_type, timestamp, data)
VALUES ('ORD-2024-001', 'created', NOW(), 
        '{"channel": "web", "device": "mobile"}'::jsonb);

-- 5. Key-Value: Invalidate cache
DELETE FROM cache WHERE key = 'inventory:789';

COMMIT;

Social Network Activity

BEGIN;

-- 1. Document: Create post
INSERT INTO posts (author_id, content)
VALUES (123, '{
  "text": "Just launched my new project!",
  "media": ["image1.jpg", "image2.jpg"],
  "tags": ["#tech", "#startup"],
  "visibility": "public"
}'::jsonb)
RETURNING id INTO post_id;

-- 2. Graph: Notify followers
INSERT INTO notifications (user_id, type, source_id)
SELECT follower_id, 'new_post', post_id
FROM follows
WHERE followee_id = 123;

-- 3. Time-Series: Log engagement metrics
INSERT INTO engagement_metrics (post_id, timestamp, metric_type, value)
VALUES (post_id, NOW(), 'created', 1);

-- 4. Relational: Update user stats
UPDATE user_stats 
SET total_posts = total_posts + 1,
    last_post_at = NOW()
WHERE user_id = 123;

COMMIT;

IoT Data Processing

BEGIN;

-- 1. Time-Series: Store sensor readings (bulk)
INSERT INTO sensor_data (device_id, timestamp, readings)
SELECT 
  'device-001',
  ts,
  jsonb_build_object(
    'temperature', 72.5 + random() * 5,
    'humidity', 45 + random() * 10,
    'pressure', 1013 + random() * 5
  )
FROM generate_series(
  NOW() - INTERVAL '1 minute',
  NOW(),
  INTERVAL '1 second'
) ts;

-- 2. Document: Update device state if anomaly detected
UPDATE devices
SET state = jsonb_set(
  state,
  '{alerts}',
  state->'alerts' || '["high_temperature"]'::jsonb
)
WHERE id = 'device-001'
  AND (SELECT readings->>'temperature' FROM sensor_data 
       WHERE device_id = 'device-001' 
       ORDER BY timestamp DESC LIMIT 1)::float > 75;

-- 3. Relational: Create alert record
INSERT INTO alerts (device_id, severity, message)
SELECT 'device-001', 'warning', 'Temperature threshold exceeded'
WHERE EXISTS (
  SELECT 1 FROM devices 
  WHERE id = 'device-001' 
  AND state->'alerts' ? 'high_temperature'
);

COMMIT;

Cross-Model Queries

Query across different data models in a single transaction:

-- Join relational, document, and graph data
SELECT 
  u.id,
  u.name,
  u.profile->>'bio' as bio,  -- Document field
  COUNT(DISTINCT p.id) as friend_count,  -- Graph traversal
  AVG(e.metrics->>'session_duration')::float as avg_session  -- Time-series
FROM users u
-- Graph: Get friends
LEFT JOIN friendships f ON u.id = f.user_id
LEFT JOIN users p ON f.friend_id = p.id
-- Time-Series: Get recent events
LEFT JOIN events e ON u.id = e.user_id 
  AND e.timestamp > NOW() - INTERVAL '30 days'
WHERE u.profile->>'account_type' = 'premium'  -- Document query
GROUP BY u.id, u.name, u.profile
HAVING COUNT(DISTINCT p.id) > 10
ORDER BY avg_session DESC;

Consistency Guarantees

Atomicity Across Models

Either all operations succeed across all models, or none do. No partial updates.

Isolation

Concurrent transactions don't see each other's intermediate states, regardless of data model.

Durability

Once committed, changes to all models are permanent and survive failures.

Foreign Key Integrity

Referential integrity works across models. Document can reference relational table.

Performance Considerations

Unified Transaction Manager

entropyDB uses a single transaction manager for all models, eliminating coordination overhead:

Traditional Multi-Database:
  - Separate transaction per DB
  - 2PC coordinator overhead
  - Network latency between DBs
  - Potential for inconsistency

entropyDB:
  - Single transaction
  - No coordination overhead
  - Local execution
  - Guaranteed consistency

Optimized Storage Engine

All models share the same LSM-tree storage engine with unified WAL, reducing I/O overhead.

Error Handling

-- All models rolled back on error
BEGIN;

INSERT INTO users (name, email) VALUES ('John', 'john@example.com');

INSERT INTO user_profiles (user_id, data)
VALUES (LASTVAL(), '{"bio": "Developer"}'::jsonb);

-- This fails due to constraint
INSERT INTO user_stats (user_id, score)
VALUES (LASTVAL(), -100);  -- Violates CHECK (score >= 0)

-- ROLLBACK automatically triggered
-- Both user and profile insertions are undone

-- In application code:
try {
  await db.query('BEGIN');
  await db.query('INSERT INTO users...');
  await db.query('INSERT INTO profiles...');
  await db.query('INSERT INTO stats...');
  await db.query('COMMIT');
} catch (error) {
  await db.query('ROLLBACK');
  console.error('Transaction failed:', error);
}

Best Practices

1. Group Related Operations

Keep logically related updates in the same transaction, even across models.

2. Minimize Transaction Scope

Only include operations that must be atomic. Keep transactions short.

3. Use Appropriate Indexes

Create indexes on foreign keys and frequently queried fields across all models.

4. Test Rollback Scenarios

Verify that your application correctly handles transaction failures and retries.

5. Monitor Transaction Duration

Long transactions hold locks across models. Set statement timeouts appropriately.

Next Steps