# Gotchas & Troubleshooting

Common problems → causes → solutions.

## Permission Errors

### 401 Unauthorized

**Error:** `"401 Unauthorized"`  
**Cause:** Token missing R2 Data Catalog permissions.  
**Solution:** Use "Admin Read & Write" token (includes catalog + storage permissions). Test with `catalog.list_namespaces()`.

### 403 Forbidden

**Error:** `"403 Forbidden"` on data files  
**Cause:** Token lacks storage permissions.  
**Solution:** Token needs both R2 Data Catalog + R2 Storage Bucket Item permissions.

### Token Rotation Issues

**Error:** New token fails after rotation.  
**Solution:** Create new token → test in staging → update prod → monitor 24h → revoke old.

## Catalog URI Issues

### 404 Not Found

**Error:** `"404 Catalog not found"`  
**Cause:** Catalog not enabled or wrong URI.  
**Solution:** Run `wrangler r2 bucket catalog enable <bucket>`. URI must be HTTPS with `/iceberg/` and case-sensitive bucket name.

### Wrong Warehouse

**Error:** Cannot create/load tables.  
**Cause:** Warehouse ≠ bucket name.  
**Solution:** Set `warehouse="bucket-name"` to match bucket exactly.

## Table and Schema Issues

### Table/Namespace Already Exists

**Error:** `"TableAlreadyExistsError"`  
**Solution:** Use try/except to load existing or check first.

### Namespace Not Found

**Error:** Cannot create table.  
**Solution:** Create namespace first: `catalog.create_namespace("ns")`

### Schema Evolution Errors

**Error:** `"422 Validation"` on schema update.  
**Cause:** Incompatible change (required field, type shrink).  
**Solution:** Only add nullable columns, compatible type widening (int→long, float→double).

## Data and Query Issues

### Empty Scan Results

**Error:** Scan returns no data.  
**Cause:** Incorrect filter or partition column.  
**Solution:** Test without filter first: `table.scan().to_pandas()`. Verify partition column names.

### Slow Queries

**Error:** Performance degrades over time.  
**Cause:** Too many small files.  
**Solution:** Check file count, compact if >1000 or avg <10MB. See [api.md](api.md#compaction).

### Type Mismatch

**Error:** `"Cannot cast"` on append.  
**Cause:** PyArrow types don't match Iceberg schema.  
**Solution:** Cast to int64 (Iceberg default), not int32. Check `table.schema()`.

## Compaction Issues

### Compaction Issues

**Problem:** File count unchanged or compaction takes hours.  
**Cause:** Target size too large, or table too big for PyIceberg.  
**Solution:** Only compact if avg <50MB. For >1TB tables, use Spark. Run during low-traffic periods.

## Maintenance Issues

### Snapshot/Orphan Issues

**Problem:** Expiration fails or orphan cleanup deletes active data.  
**Cause:** Too aggressive retention or wrong order.  
**Solution:** Always expire snapshots first with `retain_last=10`, then cleanup orphans with 3+ day threshold.

## Concurrency Issues

### Concurrent Write Conflicts

**Problem:** `CommitFailedException` with multiple writers.  
**Cause:** Optimistic locking - simultaneous commits.  
**Solution:** Add retry with exponential backoff (see [patterns.md](patterns.md#pattern-6-concurrent-writes-with-retry)).

### Stale Metadata

**Problem:** Old schema/data after external update.  
**Cause:** Cached metadata.  
**Solution:** Reload table: `table = catalog.load_table(("ns", "table"))`

## Performance Optimization

### Performance Tips

**Scans:** Use `row_filter` and `selected_fields` to reduce data scanned.  
**Partitions:** 100-1000 optimal. Avoid high cardinality (millions) or low (<10).  
**Files:** Keep 100-500MB avg. Compact if <10MB or >10k files.

## Limits

| Resource | Recommended | Impact if Exceeded |
|----------|-------------|-------------------|
| Tables/namespace | <10k | Slow list ops |
| Files/table | <100k | Slow query planning |
| Partitions/table | 100-1k | Metadata overhead |
| Snapshots/table | Expire >7d | Metadata bloat |

## Common Error Messages Reference

| Error Message | Likely Cause | Fix |
|---------------|--------------|-----|
| `401 Unauthorized` | Missing/invalid token | Check token has catalog+storage permissions |
| `403 Forbidden` | Token lacks storage permissions | Add R2 Storage Bucket Item permission |
| `404 Not Found` | Catalog not enabled or wrong URI | Run `wrangler r2 bucket catalog enable` |
| `409 Conflict` | Table/namespace already exists | Use try/except or load existing |
| `422 Unprocessable Entity` | Schema validation failed | Check type compatibility, required fields |
| `CommitFailedException` | Concurrent write conflict | Add retry logic with backoff |
| `NamespaceAlreadyExistsError` | Namespace exists | Use try/except or load existing |
| `NoSuchTableError` | Table doesn't exist | Check namespace+table name, create first |
| `TypeError: Cannot cast` | PyArrow type mismatch | Cast data to match Iceberg schema |

## Debugging Checklist

When things go wrong, check in order:

1. ✅ **Catalog enabled:** `npx wrangler r2 bucket catalog status <bucket>`
2. ✅ **Token permissions:** Both R2 Data Catalog + R2 Storage in dashboard
3. ✅ **Connection test:** `catalog.list_namespaces()` succeeds
4. ✅ **URI format:** HTTPS, includes `/iceberg/`, correct bucket name
5. ✅ **Warehouse name:** Matches bucket name exactly
6. ✅ **Namespace exists:** Create before `create_table()`
7. ✅ **Enable debug logging:** `logging.basicConfig(level=logging.DEBUG)`
8. ✅ **PyIceberg version:** `pip install --upgrade pyiceberg` (≥0.5.0)
9. ✅ **File health:** Compact if >1000 files or avg <10MB
10. ✅ **Snapshot count:** Expire if >100 snapshots

## Enable Debug Logging

```python
import logging
logging.basicConfig(level=logging.DEBUG)
# Now operations show HTTP requests/responses
```

## Resources

- [Cloudflare Community](https://community.cloudflare.com/c/developers/workers/40)
- [Cloudflare Discord](https://discord.cloudflare.com) - #r2 channel
- [PyIceberg GitHub](https://github.com/apache/iceberg-python/issues)
- [Apache Iceberg Slack](https://iceberg.apache.org/community/)

## Next Steps

- [patterns.md](patterns.md) - Working examples
- [api.md](api.md) - API reference
