-
Notifications
You must be signed in to change notification settings - Fork 33
Feat/s3 large comments #5145
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
lsabor
wants to merge
6
commits into
main
Choose a base branch
from
feat/s3-large-comments
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Feat/s3 large comments #5145
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
fc1bf35
feat: archive long private bot comment texts to S3
lsabor 0c7fedb
fix: truncate the base text column when archiving bot comments
lsabor 93a5268
adds syncing command
lsabor 7af60ec
test: cover syncing comment texts against an existing archive
lsabor fbb971b
fix: address review of the comment text archive
lsabor 25ec7e0
refactor: simplify the comment text archive commands
lsabor File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
179 changes: 179 additions & 0 deletions
179
comments/management/commands/archive_bot_comment_texts.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,179 @@ | ||
| import time | ||
| from collections.abc import Callable | ||
|
|
||
| from django.conf import settings | ||
| from django.core.management.base import BaseCommand, CommandError | ||
|
|
||
| from comments.services.text_archive import ( | ||
| ARCHIVE_AGE_DAYS, | ||
| ARCHIVE_MIN_TEXT_LENGTH, | ||
| ARCHIVE_STUB_LENGTH, | ||
| DEFAULT_BATCH_SIZE, | ||
| DEFAULT_CONCURRENCY, | ||
| S3_KEY_PREFIX, | ||
| ArchiveStats, | ||
| archive_bot_comment_texts, | ||
| check_is_enabled, | ||
| ) | ||
|
|
||
|
|
||
| def format_duration(seconds: float) -> str: | ||
| seconds = int(seconds) | ||
| hours, remainder = divmod(seconds, 3600) | ||
| minutes, seconds = divmod(remainder, 60) | ||
|
|
||
| if hours: | ||
| return f"{hours}h{minutes:02d}m" | ||
| if minutes: | ||
| return f"{minutes}m{seconds:02d}s" | ||
|
|
||
| return f"{seconds}s" | ||
|
|
||
|
|
||
| class ProgressWriter: | ||
| """ | ||
| Prints a running one-line summary with a rate and an ETA. | ||
|
|
||
| This command works through hundreds of thousands of rows over hours, so | ||
| the point is to make a long run observable rather than to look pretty. | ||
| Output is one line per batch, not a redrawn line, so it survives being | ||
| piped to a log file. | ||
| """ | ||
|
|
||
| def __init__(self, stdout, total: int = 0): | ||
| self.stdout = stdout | ||
| self.total = total | ||
| self.started = time.monotonic() | ||
|
|
||
| @property | ||
| def elapsed(self) -> float: | ||
| return time.monotonic() - self.started | ||
|
|
||
| def write(self, line: str) -> None: | ||
| self.stdout.write(line) | ||
| self.stdout.flush() | ||
|
|
||
| def update(self, done: int, summary: str, detail: str = "") -> None: | ||
| elapsed = self.elapsed | ||
| rate = done / elapsed if elapsed else 0 | ||
| percent = (done / self.total * 100) if self.total else 0 | ||
| remaining = max(self.total - done, 0) | ||
| eta = format_duration(remaining / rate) if rate else "?" | ||
|
|
||
| line = ( | ||
| f" {done:,}/{self.total:,} ({percent:.1f}%) {summary} " | ||
| f"{rate:.1f}/s elapsed {format_duration(elapsed)} eta {eta}" | ||
| ) | ||
|
|
||
| if detail: | ||
| line += f" [{detail}]" | ||
|
|
||
| self.write(line) | ||
|
|
||
|
|
||
| class Command(BaseCommand): | ||
| help = ( | ||
| "Moves the full text of private bot comments older than " | ||
| f"{ARCHIVE_AGE_DAYS} days and longer than {ARCHIVE_MIN_TEXT_LENGTH} " | ||
| f"characters to S3, leaving a {ARCHIVE_STUB_LENGTH}-character stub in " | ||
| "the database. Runs monthly as a cron job; the full text stays " | ||
| "readable through the comment-full-text endpoint." | ||
| ) | ||
|
|
||
| def add_arguments(self, parser): | ||
| parser.add_argument( | ||
| "--dry-run", | ||
| action="store_true", | ||
| help="Report what would be archived without writing to S3 or the database", | ||
| ) | ||
| parser.add_argument( | ||
| "--limit", | ||
| type=int, | ||
| default=None, | ||
| help="Maximum number of comments to archive (useful for the first backfill)", | ||
| ) | ||
| parser.add_argument( | ||
| "--batch-size", | ||
| type=int, | ||
| default=DEFAULT_BATCH_SIZE, | ||
| help=f"Comments per database update (default: {DEFAULT_BATCH_SIZE})", | ||
| ) | ||
| parser.add_argument( | ||
| "--concurrency", | ||
| type=int, | ||
| default=DEFAULT_CONCURRENCY, | ||
| help=( | ||
| "Uploads to keep in flight at once. S3 has no multi-object PUT, " | ||
| "so this is what makes a large backfill finish in minutes " | ||
| f"rather than hours (default: {DEFAULT_CONCURRENCY})" | ||
| ), | ||
| ) | ||
|
|
||
| def handle(self, *args, **options): | ||
| dry_run = options["dry_run"] | ||
|
|
||
| if not check_is_enabled(): | ||
| raise CommandError( | ||
| "AWS_STORAGE_BUCKET_COMMENTS_TEXT is not configured, " | ||
| "comment text archiving is disabled." | ||
| ) | ||
|
|
||
| progress = ProgressWriter(self.stdout) | ||
| on_progress: Callable[[ArchiveStats], None] | None = None | ||
|
|
||
| if not dry_run: | ||
| progress.write( | ||
| f"Archiving to {settings.AWS_STORAGE_BUCKET_COMMENTS_TEXT}/" | ||
| f"{S3_KEY_PREFIX}/ with concurrency {options['concurrency']}, " | ||
| f"batches of {options['batch_size']}" | ||
| ) | ||
| progress.write("Counting eligible comments...") | ||
|
|
||
| def write_progress(stats: ArchiveStats) -> None: | ||
| progress.total = stats.total | ||
| detail = ", ".join( | ||
| f"{count} {label}" | ||
| for label, count in ( | ||
| ("failed", stats.failed), | ||
| ("skipped", stats.skipped), | ||
| ) | ||
| if count | ||
| ) | ||
| progress.update( | ||
| stats.archived + stats.failed + stats.skipped, | ||
| f"{stats.chars_reclaimed:,} chars reclaimed", | ||
| detail, | ||
| ) | ||
|
|
||
| on_progress = write_progress | ||
|
|
||
| stats = archive_bot_comment_texts( | ||
| dry_run=dry_run, | ||
| limit=options["limit"], | ||
| batch_size=options["batch_size"], | ||
| concurrency=options["concurrency"], | ||
| on_progress=on_progress, | ||
| ) | ||
|
|
||
| verb = "Would archive" if dry_run else "Archived" | ||
| elapsed = "" if dry_run else f" in {format_duration(progress.elapsed)}" | ||
| progress.write( | ||
| f"{verb} {stats.archived:,} comment(s), " | ||
| f"reclaiming {stats.chars_reclaimed:,} characters{elapsed}" | ||
| ) | ||
|
|
||
| if stats.sample_ids: | ||
| sample = ", ".join(str(pk) for pk in stats.sample_ids) | ||
| progress.write(f"Sample comment ids: {sample}") | ||
|
|
||
| if stats.skipped: | ||
| self.stdout.write( | ||
| self.style.WARNING( | ||
| f"Skipped {stats.skipped} comment(s) edited during the run" | ||
| ) | ||
| ) | ||
|
|
||
| if stats.failed: | ||
| self.stdout.write( | ||
| self.style.ERROR(f"Failed to upload {stats.failed} comment(s)") | ||
| ) |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to double-check: this will only be visible in the detail view, but never in the list, right?