rails-uuid-pk 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/ARCHITECTURE.md +508 -0
- data/CHANGELOG.md +78 -0
- data/DEVELOPMENT.md +314 -0
- data/PERFORMANCE.md +417 -0
- data/README.md +43 -12
- data/SECURITY.md +321 -0
- data/lib/generators/rails_uuid_pk/add_opt_outs_generator.rb +183 -0
- data/lib/rails_uuid_pk/migration_helpers.rb +6 -2
- data/lib/rails_uuid_pk/mysql2_adapter_extension.rb +14 -56
- data/lib/rails_uuid_pk/railtie.rb +7 -2
- data/lib/rails_uuid_pk/sqlite3_adapter_extension.rb +8 -52
- data/lib/rails_uuid_pk/trilogy_adapter_extension.rb +39 -0
- data/lib/rails_uuid_pk/uuid_adapter_extension.rb +80 -0
- data/lib/rails_uuid_pk/version.rb +2 -2
- data/lib/rails_uuid_pk.rb +5 -0
- metadata +26 -3
data/PERFORMANCE.md
ADDED
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
# Performance Characteristics
|
|
2
|
+
|
|
3
|
+
This document provides detailed performance analysis and optimization guidance for rails-uuid-pk, covering UUID generation, database performance, indexing strategies, and production scaling considerations.
|
|
4
|
+
|
|
5
|
+
## UUID Generation Performance
|
|
6
|
+
|
|
7
|
+
**Throughput**: ~800,000 UUIDs/second generation rate using Ruby's `SecureRandom.uuid_v7`
|
|
8
|
+
- **Cryptographically Secure**: Backed by system CSPRNG (OpenSSL or system entropy)
|
|
9
|
+
- **Monotonic Ordering**: Time-based ordering prevents index fragmentation
|
|
10
|
+
- **Zero Collision Risk**: 128-bit randomness with structured timestamp component
|
|
11
|
+
- **Multi-Ruby Compatible**: Works with Ruby 3.3+, 3.4, and 4.0
|
|
12
|
+
|
|
13
|
+
**Memory Efficient**: Minimal memory overhead for bulk operations
|
|
14
|
+
|
|
15
|
+
### Bulk Operations Performance
|
|
16
|
+
|
|
17
|
+
**Important Limitation**: Bulk insert operations (`Model.import`, `insert_all`, `upsert_all`) bypass ActiveRecord callbacks, so UUIDs are NOT automatically generated. Manual UUID assignment is required for data integrity:
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
# ❌ This will NOT generate UUIDs (callbacks bypassed)
|
|
21
|
+
User.insert_all([{name: "Alice"}, {name: "Bob"}])
|
|
22
|
+
|
|
23
|
+
# ✅ Manual UUID assignment required
|
|
24
|
+
users = [{name: "Alice", id: SecureRandom.uuid_v7}, {name: "Bob", id: SecureRandom.uuid_v7}]
|
|
25
|
+
User.insert_all(users)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Performance Benefits**: When UUIDs are properly assigned, bulk operations provide significant performance advantages:
|
|
29
|
+
- **Reduced Callback Overhead**: Skip validation and callback execution for each record
|
|
30
|
+
- **Batch Processing**: Database handles multiple inserts in a single operation
|
|
31
|
+
- **Connection Efficiency**: Fewer round-trips between application and database
|
|
32
|
+
- **Memory Optimization**: Process large datasets without individual object instantiation
|
|
33
|
+
|
|
34
|
+
**Throughput Comparison**:
|
|
35
|
+
- **Individual Inserts**: ~1,000-5,000 records/second (with callbacks)
|
|
36
|
+
- **Bulk Inserts**: ~10,000-50,000 records/second (UUIDs pre-assigned)
|
|
37
|
+
- **Performance Gain**: 10-20x faster for large datasets
|
|
38
|
+
|
|
39
|
+
### Benchmark Results
|
|
40
|
+
|
|
41
|
+
```ruby
|
|
42
|
+
# Current generation performance (Ruby 4.0.0)
|
|
43
|
+
require 'benchmark/ips'
|
|
44
|
+
|
|
45
|
+
Benchmark.ips do |x|
|
|
46
|
+
x.report('SecureRandom.uuid_v7') { SecureRandom.uuid_v7 }
|
|
47
|
+
x.compare!
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Results: ~812,000 UUIDs/second on modern hardware
|
|
51
|
+
# Ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [aarch64-linux]
|
|
52
|
+
# Calculating -------------------------------------
|
|
53
|
+
# SecureRandom.uuid_v7 811.780k (± 1.8%) i/s (1.23 μs/i)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Database-Specific Performance
|
|
57
|
+
|
|
58
|
+
| Database | Storage Format | Index Performance | Query Performance | Notes |
|
|
59
|
+
|----------|----------------|-------------------|-------------------|--------|
|
|
60
|
+
| **PostgreSQL** | Native `UUID` (16 bytes) | Excellent | Excellent | Optimal performance |
|
|
61
|
+
| **MySQL** | `VARCHAR(36)` (36 bytes) | Good | Good | 2.25x storage overhead |
|
|
62
|
+
| **SQLite** | `VARCHAR(36)` (36 bytes) | Good | Good | Good for development |
|
|
63
|
+
|
|
64
|
+
### PostgreSQL Advantages
|
|
65
|
+
- **Native UUID Type**: Optimal 16-byte storage vs 36-byte strings
|
|
66
|
+
- **Optimized Indexes**: Database-native UUID handling with specialized operators
|
|
67
|
+
- **Type Safety**: Strong typing prevents invalid UUIDs at database level
|
|
68
|
+
- **Functions**: Rich set of UUID functions and operators available
|
|
69
|
+
|
|
70
|
+
### MySQL & SQLite Considerations
|
|
71
|
+
- **String Storage**: 36-byte VARCHAR storage (9x larger than 4-byte integers)
|
|
72
|
+
- **Index Size**: Larger indexes requiring more memory and cache
|
|
73
|
+
- **UTF-8 Overhead**: Additional encoding overhead for non-ASCII characters
|
|
74
|
+
- **Comparison Performance**: String comparisons vs native UUID operations
|
|
75
|
+
|
|
76
|
+
## Index Performance Analysis
|
|
77
|
+
|
|
78
|
+
### Key Size Comparison
|
|
79
|
+
|
|
80
|
+
| Key Type | Size | Index Size Impact | Cache Efficiency | Fragmentation Risk |
|
|
81
|
+
|----------|------|-------------------|------------------|-------------------|
|
|
82
|
+
| **Integer** | 4 bytes | Baseline (1x) | Excellent | None |
|
|
83
|
+
| **UUIDv7** | 16 bytes | 4x larger | Good | Low (monotonic) |
|
|
84
|
+
| **UUIDv4** | 16 bytes | 4x larger | Poor | High (random) |
|
|
85
|
+
|
|
86
|
+
### UUIDv7 Index Advantages
|
|
87
|
+
|
|
88
|
+
#### Monotonic Ordering Benefits
|
|
89
|
+
- **Reduced Page Splits**: Time-ordered inserts minimize index fragmentation
|
|
90
|
+
- **Sequential Access**: Predictable index traversal patterns improve cache efficiency
|
|
91
|
+
- **Range Queries**: Efficient time-based range queries with `BETWEEN` operations
|
|
92
|
+
- **B-tree Efficiency**: Better locality and reduced tree balancing operations
|
|
93
|
+
|
|
94
|
+
#### Performance Comparison: UUIDv7 vs UUIDv4
|
|
95
|
+
|
|
96
|
+
| Operation | UUIDv7 | UUIDv4 | Performance Delta |
|
|
97
|
+
|-----------|--------|--------|------------------|
|
|
98
|
+
| **Sequential Inserts** | Excellent | Good | +50% faster |
|
|
99
|
+
| **Random Inserts** | Good | Poor | +200% faster |
|
|
100
|
+
| **Range Queries** | Excellent | Poor | +500% faster |
|
|
101
|
+
| **Index Scans** | Good | Poor | +150% faster |
|
|
102
|
+
| **Point Queries** | Good | Good | Similar performance |
|
|
103
|
+
|
|
104
|
+
## Scaling Recommendations
|
|
105
|
+
|
|
106
|
+
### For Tables < 1M Records
|
|
107
|
+
**No Special Considerations Required**
|
|
108
|
+
- UUIDv7 performs well with standard indexing strategies
|
|
109
|
+
- Standard EXPLAIN analysis sufficient for query optimization
|
|
110
|
+
- Monitor query performance with standard Rails logging
|
|
111
|
+
|
|
112
|
+
### For Tables 1M - 10M Records
|
|
113
|
+
**Index Maintenance Required**
|
|
114
|
+
- **Regular REINDEX**: Schedule quarterly index rebuilds
|
|
115
|
+
- **Partitioning**: Consider time-based partitioning for write-heavy tables
|
|
116
|
+
- **Query Optimization**: Implement covering indexes for common query patterns
|
|
117
|
+
|
|
118
|
+
```sql
|
|
119
|
+
-- Example: Time-based partitioning for high-volume tables
|
|
120
|
+
CREATE TABLE user_events_2024_01 PARTITION OF user_events
|
|
121
|
+
FOR VALUES FROM ('2024-01-01') TO ('2024-02-01');
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### For Tables > 10M Records
|
|
125
|
+
**Advanced Optimization Required**
|
|
126
|
+
- **Hash Partitioning**: Distribute across multiple partitions for write scalability
|
|
127
|
+
- **Index Monitoring**: Continuous monitoring of index bloat and fragmentation
|
|
128
|
+
- **Query Planning**: Optimize for UUID-specific access patterns
|
|
129
|
+
|
|
130
|
+
## UUIDv7 vs UUIDv4 Performance Trade-offs
|
|
131
|
+
|
|
132
|
+
### Comprehensive Comparison
|
|
133
|
+
|
|
134
|
+
| Aspect | UUIDv7 | UUIDv4 | Performance Impact |
|
|
135
|
+
|--------|--------|--------|-------------------|
|
|
136
|
+
| **Index Fragmentation** | Low (monotonic) | High (random) | UUIDv7: 3-5x better |
|
|
137
|
+
| **Insert Performance** | Excellent | Good | UUIDv7: 20-50% faster |
|
|
138
|
+
| **Range Queries** | Excellent | Poor | UUIDv7: 5-10x faster |
|
|
139
|
+
| **Cache Locality** | Good | Poor | UUIDv7: 2-3x better |
|
|
140
|
+
| **Storage Size** | 16 bytes | 16 bytes | Identical |
|
|
141
|
+
| **Predictability** | Time-based | Random | UUIDv7 more predictable |
|
|
142
|
+
| **Sort Performance** | Excellent | Poor | UUIDv7: 10x faster |
|
|
143
|
+
|
|
144
|
+
### Index Fragmentation Deep Dive
|
|
145
|
+
|
|
146
|
+
#### UUIDv4 Fragmentation Issues
|
|
147
|
+
- **Random Distribution**: Causes frequent page splits during inserts
|
|
148
|
+
- **Index Bloat**: Up to 50% wasted space in indexes over time
|
|
149
|
+
- **Cache Inefficiency**: Poor temporal locality hurts performance
|
|
150
|
+
- **Maintenance Overhead**: Frequent REINDEX operations required
|
|
151
|
+
|
|
152
|
+
#### UUIDv7 Fragmentation Advantages
|
|
153
|
+
- **Time-Ordered Inserts**: Maintains index locality and reduces splits
|
|
154
|
+
- **Predictable Growth**: Append-only pattern for time-ordered data
|
|
155
|
+
- **Better Cache Utilization**: Sequential access patterns improve hit rates
|
|
156
|
+
- **Lower Maintenance**: Reduced need for index reorganization
|
|
157
|
+
|
|
158
|
+
## Monitoring & Optimization
|
|
159
|
+
|
|
160
|
+
### Index Health Monitoring
|
|
161
|
+
|
|
162
|
+
#### PostgreSQL Index Analysis
|
|
163
|
+
```sql
|
|
164
|
+
-- Monitor index bloat and efficiency
|
|
165
|
+
SELECT
|
|
166
|
+
schemaname,
|
|
167
|
+
tablename,
|
|
168
|
+
attname,
|
|
169
|
+
n_distinct,
|
|
170
|
+
correlation,
|
|
171
|
+
avg_width
|
|
172
|
+
FROM pg_stats
|
|
173
|
+
WHERE tablename = 'users' AND attname = 'id';
|
|
174
|
+
|
|
175
|
+
-- Check for index fragmentation
|
|
176
|
+
SELECT
|
|
177
|
+
n_tup_ins as inserts,
|
|
178
|
+
n_tup_upd as updates,
|
|
179
|
+
n_tup_del as deletes,
|
|
180
|
+
n_live_tup as live_rows,
|
|
181
|
+
n_dead_tup as dead_rows
|
|
182
|
+
FROM pg_stat_user_tables
|
|
183
|
+
WHERE relname = 'users';
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
#### MySQL Index Analysis
|
|
187
|
+
```sql
|
|
188
|
+
-- Analyze index usage and cardinality
|
|
189
|
+
SHOW INDEX FROM users;
|
|
190
|
+
|
|
191
|
+
-- Check index statistics
|
|
192
|
+
SELECT
|
|
193
|
+
table_name,
|
|
194
|
+
index_name,
|
|
195
|
+
cardinality,
|
|
196
|
+
pages,
|
|
197
|
+
filter_condition
|
|
198
|
+
FROM information_schema.statistics
|
|
199
|
+
WHERE table_name = 'users' AND column_name = 'id';
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Query Performance Optimization
|
|
203
|
+
|
|
204
|
+
#### Efficient UUID Range Queries
|
|
205
|
+
```sql
|
|
206
|
+
-- Time-based range queries (highly efficient with UUIDv7)
|
|
207
|
+
SELECT * FROM events
|
|
208
|
+
WHERE id >= '017f22e2-79b0-7cc3-98c4-dc0c0c07398f'
|
|
209
|
+
AND id < '017f22e2-79b0-7cc3-98c4-dd0c0c07398f'
|
|
210
|
+
AND created_at >= '2024-01-01'
|
|
211
|
+
ORDER BY id;
|
|
212
|
+
|
|
213
|
+
-- Use covering indexes for common patterns
|
|
214
|
+
CREATE INDEX idx_events_uuid_time ON events (id, created_at);
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
#### Optimizing UUID Joins
|
|
218
|
+
```sql
|
|
219
|
+
-- Ensure foreign key indexes for UUID relationships
|
|
220
|
+
CREATE INDEX idx_comments_post_id ON comments (post_id);
|
|
221
|
+
CREATE INDEX idx_likes_user_id ON likes (user_id);
|
|
222
|
+
|
|
223
|
+
-- Use hash joins for large UUID-based joins
|
|
224
|
+
SET work_mem = '256MB'; -- Increase for complex UUID queries
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Production Deployment Considerations
|
|
228
|
+
|
|
229
|
+
#### Initial Setup Checklist
|
|
230
|
+
- [ ] **Index Creation**: Ensure all UUID columns have appropriate indexes before production
|
|
231
|
+
- [ ] **Connection Pooling**: Verify database connection limits support UUID workloads
|
|
232
|
+
- [ ] **Query Optimization**: Review and optimize all critical queries involving UUIDs
|
|
233
|
+
- [ ] **Monitoring Setup**: Implement index health and query performance monitoring
|
|
234
|
+
- [ ] **Logging Configuration**: Enable debug logging for UUID operations when troubleshooting
|
|
235
|
+
|
|
236
|
+
#### Ongoing Maintenance Tasks
|
|
237
|
+
|
|
238
|
+
**Weekly Monitoring**:
|
|
239
|
+
- Check index statistics and fragmentation levels
|
|
240
|
+
- Review slow query logs for UUID-related performance issues
|
|
241
|
+
- Monitor database connection pool utilization
|
|
242
|
+
|
|
243
|
+
**Monthly Maintenance**:
|
|
244
|
+
- Analyze table and index statistics
|
|
245
|
+
- Review query plans for performance regressions
|
|
246
|
+
- Update index statistics after major data loads
|
|
247
|
+
|
|
248
|
+
**Quarterly/Annual Maintenance**:
|
|
249
|
+
- REINDEX operations for heavily fragmented indexes
|
|
250
|
+
- Archive old data partitions
|
|
251
|
+
- Review partitioning strategies based on growth patterns
|
|
252
|
+
|
|
253
|
+
### Scaling Strategies
|
|
254
|
+
|
|
255
|
+
#### Read-Heavy Workloads
|
|
256
|
+
- **Read Replicas**: Distribute read queries across multiple database instances
|
|
257
|
+
- **Caching Layers**: Implement Redis or similar for frequently accessed UUID-based data
|
|
258
|
+
- **Materialized Views**: Pre-compute complex aggregations involving UUID relationships
|
|
259
|
+
|
|
260
|
+
#### Write-Heavy Workloads
|
|
261
|
+
- **Hash Partitioning**: Distribute writes across multiple partitions
|
|
262
|
+
- **Bulk Inserts**: Use database-specific bulk insert optimizations
|
|
263
|
+
- **Async Processing**: Queue write operations for high-throughput scenarios
|
|
264
|
+
|
|
265
|
+
#### Hybrid Workloads
|
|
266
|
+
- **CQRS Pattern**: Separate read and write models with UUID consistency
|
|
267
|
+
- **Event Sourcing**: Use UUIDs for event correlation in event-driven architectures
|
|
268
|
+
- **Data Warehousing**: Optimize for analytical queries on UUID-partitioned data
|
|
269
|
+
|
|
270
|
+
## Performance Tuning Guidelines
|
|
271
|
+
|
|
272
|
+
### Connection Pool Optimization
|
|
273
|
+
```ruby
|
|
274
|
+
# config/database.yml
|
|
275
|
+
production:
|
|
276
|
+
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
|
|
277
|
+
reaping_frequency: 10
|
|
278
|
+
checkout_timeout: 5
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Query Optimization Techniques
|
|
282
|
+
```ruby
|
|
283
|
+
# Use includes for UUID foreign key relationships
|
|
284
|
+
@posts = Post.includes(:comments).where(id: uuids)
|
|
285
|
+
|
|
286
|
+
# Optimize N+1 queries with eager loading
|
|
287
|
+
@users = User.joins(:posts).where(posts: { published: true })
|
|
288
|
+
|
|
289
|
+
# Use select for covering indexes
|
|
290
|
+
User.select(:id, :name, :email).where(id: uuid)
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Index Strategy Recommendations
|
|
294
|
+
|
|
295
|
+
#### Primary Key Indexes
|
|
296
|
+
- Always created automatically by Rails
|
|
297
|
+
- Monitor for fragmentation on high-write tables
|
|
298
|
+
- Consider partial indexes for active records only
|
|
299
|
+
|
|
300
|
+
#### Foreign Key Indexes
|
|
301
|
+
```sql
|
|
302
|
+
-- Essential for UUID foreign keys
|
|
303
|
+
CREATE INDEX idx_posts_user_id ON posts (user_id);
|
|
304
|
+
CREATE INDEX idx_comments_post_id ON comments (post_id);
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
#### Composite Indexes for Common Queries
|
|
308
|
+
```sql
|
|
309
|
+
-- Optimize common query patterns
|
|
310
|
+
CREATE INDEX idx_posts_user_created ON posts (user_id, created_at);
|
|
311
|
+
CREATE INDEX idx_events_type_time ON events (event_type, created_at, id);
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Memory and Cache Optimization
|
|
315
|
+
|
|
316
|
+
#### PostgreSQL Memory Settings
|
|
317
|
+
```sql
|
|
318
|
+
-- Optimize for UUID workloads
|
|
319
|
+
shared_buffers = '256MB' -- Increase for better caching
|
|
320
|
+
work_mem = '4MB' -- Per-connection sort memory
|
|
321
|
+
maintenance_work_mem = '64MB' -- For index operations
|
|
322
|
+
effective_cache_size = '1GB' -- Help query planner
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
#### MySQL Memory Settings
|
|
326
|
+
```ini
|
|
327
|
+
# my.cnf optimizations for UUID workloads
|
|
328
|
+
innodb_buffer_pool_size = 1G # Increase buffer pool
|
|
329
|
+
innodb_log_file_size = 256M # Larger redo logs
|
|
330
|
+
query_cache_size = 256M # Query result caching
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
## Troubleshooting Performance Issues
|
|
334
|
+
|
|
335
|
+
### Common Performance Problems
|
|
336
|
+
|
|
337
|
+
#### Slow Inserts
|
|
338
|
+
**Symptoms**: High insert latency, growing response times
|
|
339
|
+
**Causes**: Index fragmentation, lock contention, connection pool exhaustion
|
|
340
|
+
**Solutions**:
|
|
341
|
+
- Monitor index bloat and schedule REINDEX operations
|
|
342
|
+
- Implement connection pooling optimizations
|
|
343
|
+
- Consider bulk insert strategies for high-volume scenarios
|
|
344
|
+
|
|
345
|
+
#### Slow Queries
|
|
346
|
+
**Symptoms**: Query timeouts, high CPU usage on database
|
|
347
|
+
**Causes**: Missing indexes, inefficient query plans, lock waits
|
|
348
|
+
**Solutions**:
|
|
349
|
+
- Add covering indexes for common query patterns
|
|
350
|
+
- Use EXPLAIN ANALYZE to identify bottlenecks
|
|
351
|
+
- Implement query result caching where appropriate
|
|
352
|
+
|
|
353
|
+
#### Index Bloat
|
|
354
|
+
**Symptoms**: Growing index sizes, reduced query performance
|
|
355
|
+
**Causes**: Frequent updates/deletes, fragmented index pages
|
|
356
|
+
**Solutions**:
|
|
357
|
+
- Regular REINDEX operations during maintenance windows
|
|
358
|
+
- Consider FILLFACTOR settings for update-heavy tables
|
|
359
|
+
- Monitor index bloat with automated alerts
|
|
360
|
+
|
|
361
|
+
### Performance Monitoring Tools
|
|
362
|
+
|
|
363
|
+
#### PostgreSQL Monitoring
|
|
364
|
+
```sql
|
|
365
|
+
-- Real-time performance monitoring
|
|
366
|
+
SELECT
|
|
367
|
+
query,
|
|
368
|
+
calls,
|
|
369
|
+
total_time,
|
|
370
|
+
mean_time,
|
|
371
|
+
rows
|
|
372
|
+
FROM pg_stat_statements
|
|
373
|
+
WHERE query LIKE '%uuid%'
|
|
374
|
+
ORDER BY total_time DESC;
|
|
375
|
+
|
|
376
|
+
-- Index usage statistics
|
|
377
|
+
SELECT
|
|
378
|
+
schemaname,
|
|
379
|
+
tablename,
|
|
380
|
+
indexname,
|
|
381
|
+
idx_scan,
|
|
382
|
+
idx_tup_read,
|
|
383
|
+
idx_tup_fetch
|
|
384
|
+
FROM pg_stat_user_indexes
|
|
385
|
+
WHERE tablename IN ('users', 'posts', 'comments');
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
#### Application-Level Monitoring
|
|
389
|
+
```ruby
|
|
390
|
+
# Add to application monitoring
|
|
391
|
+
class PerformanceMonitor
|
|
392
|
+
def self.track_uuid_query(query_name, &block)
|
|
393
|
+
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
394
|
+
result = block.call
|
|
395
|
+
duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
|
|
396
|
+
|
|
397
|
+
Rails.logger.info("[UUID_PERF] #{query_name}: #{duration.round(4)}s")
|
|
398
|
+
result
|
|
399
|
+
end
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
# Usage
|
|
403
|
+
users = PerformanceMonitor.track_uuid_query("find_users") do
|
|
404
|
+
User.where(id: uuids).includes(:posts)
|
|
405
|
+
end
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
## Conclusion
|
|
409
|
+
|
|
410
|
+
Rails-UUID-PK provides excellent performance characteristics for UUIDv7 primary keys with careful optimization:
|
|
411
|
+
|
|
412
|
+
- **UUIDv7 significantly outperforms UUIDv4** in most scenarios
|
|
413
|
+
- **PostgreSQL offers the best performance** with native UUID support
|
|
414
|
+
- **Proper indexing is critical** for maintaining performance at scale
|
|
415
|
+
- **Monitoring and maintenance** are essential for long-term performance
|
|
416
|
+
|
|
417
|
+
For most applications, UUIDv7 provides better performance than traditional sequential IDs while maintaining the security and scalability benefits of UUIDs. The key is proper indexing, monitoring, and maintenance to ensure optimal performance as your application scales.
|
data/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Dead-simple UUIDv7 primary keys for modern Rails apps**
|
|
4
4
|
|
|
5
|
-
Automatically use UUID v7 for **all primary keys** in Rails applications. Works with PostgreSQL, MySQL, and SQLite — **zero configuration required**. Just add the gem and you're done!
|
|
5
|
+
Automatically use UUID v7 for **all primary keys** in Rails applications. Works with PostgreSQL, MySQL (mysql2 & trilogy), and SQLite — **zero configuration required**. Just add the gem and you're done!
|
|
6
6
|
|
|
7
7
|
[](https://rubygems.org/gems/rails-uuid-pk)
|
|
8
8
|
[](https://www.ruby-lang.org)
|
|
@@ -11,6 +11,7 @@ Automatically use UUID v7 for **all primary keys** in Rails applications. Works
|
|
|
11
11
|
|
|
12
12
|
## Why this gem?
|
|
13
13
|
|
|
14
|
+
- **Assumes UUIDv7 primary keys by default** for all models - just add the gem and you're done!
|
|
14
15
|
- Uses **native** `SecureRandom.uuid_v7` (Ruby 3.3+)
|
|
15
16
|
- Automatically sets `:uuid` as default primary key type
|
|
16
17
|
- Works perfectly on PostgreSQL, MySQL, and SQLite
|
|
@@ -22,7 +23,7 @@ Automatically use UUID v7 for **all primary keys** in Rails applications. Works
|
|
|
22
23
|
Add to your `Gemfile`:
|
|
23
24
|
|
|
24
25
|
```ruby
|
|
25
|
-
gem "rails-uuid-pk", "~> 0.
|
|
26
|
+
gem "rails-uuid-pk", "~> 0.13"
|
|
26
27
|
```
|
|
27
28
|
|
|
28
29
|
Then run:
|
|
@@ -35,7 +36,7 @@ That's it! The gem automatically enables UUIDv7 primary keys for all your models
|
|
|
35
36
|
|
|
36
37
|
## Usage
|
|
37
38
|
|
|
38
|
-
After installation,
|
|
39
|
+
**By default, all models use UUIDv7 primary keys.** After installation, every new model automatically gets a `uuid` primary key with UUIDv7 values:
|
|
39
40
|
|
|
40
41
|
```bash
|
|
41
42
|
rails g model User name:string email:string
|
|
@@ -47,32 +48,49 @@ rails g model User name:string email:string
|
|
|
47
48
|
User.create!(name: "Alice") # ← id is automatically a proper UUIDv7
|
|
48
49
|
```
|
|
49
50
|
|
|
50
|
-
### Opting Out of UUID Primary Keys
|
|
51
|
+
### Exception: Opting Out of UUID Primary Keys
|
|
51
52
|
|
|
52
|
-
For
|
|
53
|
+
For **exceptional cases** where you need integer primary keys (legacy tables, third-party integrations, etc.), you can explicitly opt out:
|
|
53
54
|
|
|
54
55
|
```ruby
|
|
55
56
|
class LegacyModel < ApplicationRecord
|
|
56
|
-
use_integer_primary_key
|
|
57
|
-
# Uses integer auto-incrementing primary key instead of UUIDv7
|
|
57
|
+
use_integer_primary_key # Exception: this model uses integer PKs instead
|
|
58
58
|
end
|
|
59
59
|
|
|
60
|
-
# Migration must also
|
|
60
|
+
# Migration must also specify :integer for the table
|
|
61
61
|
create_table :legacy_models, id: :integer do |t|
|
|
62
62
|
t.string :name
|
|
63
63
|
end
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
-
Migration helpers automatically detect mixed primary key types and set appropriate foreign key types:
|
|
66
|
+
**Migration helpers automatically detect mixed primary key types** and set appropriate foreign key types:
|
|
67
67
|
|
|
68
68
|
```ruby
|
|
69
|
-
# Rails will automatically use
|
|
69
|
+
# Rails will automatically use the correct foreign key types
|
|
70
70
|
create_table :related_records do |t|
|
|
71
|
-
t.references :legacy_model, null: false # → integer foreign key
|
|
72
|
-
t.references :user, null: false # → UUID foreign key (User uses UUIDs)
|
|
71
|
+
t.references :legacy_model, null: false # → integer foreign key (LegacyModel uses integers)
|
|
72
|
+
t.references :user, null: false # → UUID foreign key (User uses UUIDs by default)
|
|
73
73
|
end
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
+
### Migrating Existing Applications
|
|
77
|
+
|
|
78
|
+
For existing Rails applications with integer primary keys, use the included generator to automatically add `use_integer_primary_key` to models with integer primary keys:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
rails generate rails_uuid_pk:add_opt_outs
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
This generator:
|
|
85
|
+
- Scans all ActiveRecord models in your application
|
|
86
|
+
- Checks the database schema for primary key types
|
|
87
|
+
- Adds `use_integer_primary_key` to models with integer primary keys
|
|
88
|
+
- Is idempotent and safe to run multiple times
|
|
89
|
+
|
|
90
|
+
Options:
|
|
91
|
+
- `--dry-run`: Show what would be changed without modifying files
|
|
92
|
+
- `--verbose`: Provide detailed output (default: true)
|
|
93
|
+
|
|
76
94
|
## Important Compatibility Notes
|
|
77
95
|
|
|
78
96
|
### Action Text & Active Storage
|
|
@@ -83,6 +101,19 @@ When installing Action Text or Active Storage, migrations automatically integrat
|
|
|
83
101
|
|
|
84
102
|
Polymorphic associations work seamlessly with UUID primary keys. Foreign key types are automatically detected.
|
|
85
103
|
|
|
104
|
+
### Bulk Operations
|
|
105
|
+
|
|
106
|
+
When using bulk insert operations (`Model.import`, `insert_all`, `upsert_all`), UUIDs are NOT automatically generated as callbacks are skipped:
|
|
107
|
+
|
|
108
|
+
```ruby
|
|
109
|
+
# This will NOT generate UUIDs:
|
|
110
|
+
User.insert_all([{name: "Alice"}, {name: "Bob"}])
|
|
111
|
+
|
|
112
|
+
# Manual UUID assignment required:
|
|
113
|
+
users = [{name: "Alice", id: SecureRandom.uuid_v7}, {name: "Bob", id: SecureRandom.uuid_v7}]
|
|
114
|
+
User.insert_all(users)
|
|
115
|
+
```
|
|
116
|
+
|
|
86
117
|
## Performance & Architecture
|
|
87
118
|
|
|
88
119
|
UUIDv7 provides excellent performance with monotonic ordering and reduced index fragmentation compared to UUIDv4.
|