|
42 | 42 | from src.data import crypto_box |
43 | 43 |
|
44 | 44 | # ── Config ───────────────────────────────────────────────────────────────── |
45 | | -SHARD_TARGET_COMPRESSED = 5 * 1024 * 1024 # 5 MB per shard (compressed) |
| 45 | +STABLE_SHARD_TARGET_COMPRESSED = 5 * 1024 * 1024 # 5 MB per stable shard |
| 46 | +ACTIVE_SHARD_TARGET_COMPRESSED = 3 * 1024 * 1024 # 3 MB per active shard |
46 | 47 | ACTIVE_WINDOW_DAYS = 14 # lectures updated within this window → "active" |
47 | 48 |
|
48 | 49 |
|
@@ -141,43 +142,40 @@ def main(): |
141 | 142 | print(f"Stable courses: {len(stable)} ({sum(c['size'] for c in stable)/1024:.0f}KB)") |
142 | 143 |
|
143 | 144 | # ── Step 2: Group into shards ───────────────────────────────────────── |
144 | | - target_uncompressed = SHARD_TARGET_COMPRESSED * COMPRESSION_RATIO_GUESS |
145 | 145 | groups: list[list[str]] = [] |
146 | 146 |
|
147 | | - def _flush(group: list[str], current_size: int) -> None: |
148 | | - if group: |
149 | | - groups.append(list(group)) |
150 | | - |
151 | | - # Group 1: each active course → its own shard (if big) or grouped with |
152 | | - # other active courses |
153 | | - active_remaining = list(active) |
154 | | - while active_remaining: |
155 | | - group: list[str] = [] |
156 | | - group_size = 0 |
157 | | - while active_remaining: |
158 | | - c = active_remaining[0] |
159 | | - sz = c["size"] |
160 | | - if group and group_size + sz > target_uncompressed: |
161 | | - break |
162 | | - group.append(c["course_id"]) |
163 | | - group_size += sz |
164 | | - active_remaining.pop(0) |
165 | | - _flush(group, group_size) |
166 | | - |
167 | | - # Group 2: pack stable courses with first-fit |
168 | | - remaining = list(stable) |
169 | | - while remaining: |
170 | | - group: list[str] = [] |
171 | | - group_size = 0 |
172 | | - for c in list(remaining): |
173 | | - sz = c["size"] |
174 | | - if group_size + sz <= target_uncompressed: |
175 | | - group.append(c["course_id"]) |
176 | | - group_size += sz |
177 | | - remaining.remove(c) |
178 | | - # If a single course exceeds the target, it still gets its own |
179 | | - # shard (courses are never split) |
180 | | - _flush(group, group_size) |
| 147 | + def _pack(courses: list[dict], target: int) -> list[list[str]]: |
| 148 | + """First-fit pack course_ids into shards at most ``target`` bytes. |
| 149 | + A course exceeding the target gets its own shard (never split).""" |
| 150 | + result: list[list[str]] = [] |
| 151 | + remaining = list(courses) |
| 152 | + while remaining: |
| 153 | + group: list[str] = [] |
| 154 | + group_size = 0 |
| 155 | + for c in list(remaining): |
| 156 | + sz = c["size"] |
| 157 | + if group_size + sz <= target: |
| 158 | + group.append(c["course_id"]) |
| 159 | + group_size += sz |
| 160 | + remaining.remove(c) |
| 161 | + # Single oversized course → own shard (loop will pick it up |
| 162 | + # on next iteration when group is empty). |
| 163 | + result.append(group) |
| 164 | + return result |
| 165 | + |
| 166 | + # Active courses → 3 MB shards (they change frequently, small shards |
| 167 | + # minimize update payload). |
| 168 | + active_target = ACTIVE_SHARD_TARGET_COMPRESSED * COMPRESSION_RATIO_GUESS |
| 169 | + for g in _pack(active, active_target): |
| 170 | + if g: |
| 171 | + groups.append(g) |
| 172 | + |
| 173 | + # Stable courses → 5 MB shards (they never change, larger shards mean |
| 174 | + # fewer files with stable hashes). |
| 175 | + stable_target = STABLE_SHARD_TARGET_COMPRESSED * COMPRESSION_RATIO_GUESS |
| 176 | + for g in _pack(stable, stable_target): |
| 177 | + if g: |
| 178 | + groups.append(g) |
181 | 179 |
|
182 | 180 | if not groups: |
183 | 181 | groups = [[]] # at least one (empty) shard |
|
0 commit comments