Page MenuHomePhabricator

Enable merge-on-read for wmf_content.mediawiki_content_history_v1
Closed, ResolvedPublic

Description

wmf_content.mediawiki_content_history_v1 is one of our biggest tables, containing all revisions for all wikis over all of wikitime.

On T383816: Calculate rough HDFS storage requirements for wmf_content.mediawiki_content_history_v1, we estimated the following sizes for respective Iceberg snapshot retention periods:

For wmf_content.mediawiki_content_history_v1 as it stands today:

retention period7 days30 days60 days90 days
size in TBs36.789.6158.6227.6

For wmf_content.mediawiki_content_history_v1, assuming merge-on-read enabled with weekly table maintenance:

retention period7 days30 days60 days90 days
size in TBs25.240.360.079.7

We have now fixed the blocker that prevented us from enabling merge-on-read, namely T391280: Modify table maintenance mechanism to support Iceberg's rewrite_position_delete_files(). Additionally, we now have wmf_content.mediawiki_content_current_v1 close to production. This is a much smaller table, but it does have merge-on-read enabled and it is WAD.

Thus, in this task we want to:

  • Enable merge-on-read on wmf_content.mediawiki_content_history_v1.
  • Add rewrite_position_delete_files() table maintenance to the above.
  • Monitor the performance of all writes, see if we eliminate the number one write latency issue: skewed data on the last shuffle when writing to the table via MERGE INTO. The skew should be eliminated when doing merge-on-read, since we no longer need to rewrite existing files, and we expect to win ~6 hours back, making the job go from ~7.6h - ~6h = 1.6 h, a 475% runtime performance increase.
  • Monitor down stream wmf_content.mediawiki_content_current_v1, which consumes the history table daily, for perf regressions.

Details

Related Changes in GitLab:
TitleReferenceAuthorSource BranchDest Branch
analytics: change mw-content-history-v1 table maintenance back to weeklyrepos/data-engineering/airflow-dags!1350xcollazomwch-tbl-maintenance-back-to-weeklymain
Go back to copy-on-write for mw-content-history.repos/data-engineering/mediawiki-content-pipelines!68xcollazoback-to-copy-on-writemain
analytics: deprioritize maintenance for mw-content-historyrepos/data-engineering/airflow-dags!1292xcollazodeprioritize-tbl-maintenancemain
analytics: do daily instead of weekly table maintenance on mw-content-history table.repos/data-engineering/airflow-dags!1287xcollazoaggressive-tbl-maintenance-for-mw-content-historymain
analytics: add rewrite_position_delete_files() for mw_content_historyrepos/data-engineering/airflow-dags!1279xcollazoadd-table-maintenance-for-deletesmain
DDL to enable merge-on-read for all mutations on mw content tables.repos/data-engineering/mediawiki-content-pipelines!64xcollazomodify-tblpropertiesmain
Customize query in GitLab

Event Timeline

While preparing to do the work for this ticket, we noticed a couple discrepancies in our content tables TBLPROPERTIES that we want to fix first:

wmf_content.mediawiki_content_current_v1:

spark-sql (default)> describe formatted wmf_content.mediawiki_content_current_v1;
...
Table Properties	[current-snapshot-id=8649756776621205924,format=iceberg/parquet,write.format.default=parquet,write.merge.mode=merge-on-read,write.target-file-size-bytes=134217728]	
Time taken: 0.19 seconds, Fetched 36 row(s)

wmf_content.mediawiki_content_history_v1:

spark-sql (default)> describe formatted wmf_content.mediawiki_content_history_v1;
...
Table Properties	[current-snapshot-id=8044191397808821822,format=iceberg/parquet,sort-order=wiki_id ASC NULLS FIRST, page_id ASC NULLS FIRST, revision_dt ASC NULLS FIRST,write.distribution-mode=range,write.format.default=parquet,write.metadata.delete-after-commit.enabled=true,write.metadata.previous-versions-max=10,write.target-file-size-bytes=134217728]	
Time taken: 0.109 seconds, Fetched 38 row(s)

Discrepancies:

  1. We forgot to set write.metadata.delete-after-commit.enabled = true for wmf_content.mediawiki_content_current_v1. We want this enabled to avoid hoarding old metadata files.
  2. The default of write.metadata.previous-versions-max = 100 is reasonable, but on wmf_content.mediawiki_content_history_v1 we have it set at write.metadata.previous-versions-max = 10, for no particular reason. Let's change that to the default.
  3. merge-on-read is currently enabled on wmf_content.mediawiki_content_current_v1 only via write.merge.mode=merge-on-read. This means that MERGE INTO commands will apply with merge-on-read, but UPDATES and DELETEs will not. Although we currently only write to this table via MERGE INTO, to avoid potential confusion in the future, let's set them all to merge-on-read. We will do the same for wmf_content.mediawiki_content_history_v1 (for this table we do MERGE INTOs and also DELETEs).

So the final ALTERs so far look like so:

For wmf_content.mediawiki_content_current_v1:

ALTER TABLE wmf_content.mediawiki_content_current_v1 SET TBLPROPERTIES (
    'write.metadata.delete-after-commit.enabled'='true',
    'write.delete.mode'='merge-on-read',
    'write.merge.mode'='merge-on-read',
    'write.update.mode'='merge-on-read',
);

For wmf_content.mediawiki_content_history_v1:

ALTER TABLE wmf_content.mediawiki_content_history_v1 SET TBLPROPERTIES (
    'write.delete.mode'='merge-on-read',
    'write.merge.mode'='merge-on-read',
    'write.update.mode'='merge-on-read',
);

ALTER TABLE wmf_content.mediawiki_content_history_v1 UNSET TBLPROPERTIES ('write.metadata.previous-versions-max');

(Equivalent changes will also be done for the table CREATE statements in an MR)

xcollazo changed the task status from Open to In Progress.Apr 30 2025, 7:59 PM

Prepare:

ssh an-launcher1002.eqiad.wmnet

sudo -u analytics bash

kerberos-run-command analytics spark3-sql

wmf_content.mediawiki_content_current_v1:

spark-sql (default)> ALTER TABLE wmf_content.mediawiki_content_current_v1 SET TBLPROPERTIES (
                   >     'write.metadata.delete-after-commit.enabled'='true',
                   >     'write.delete.mode'='merge-on-read',
                   >     'write.merge.mode'='merge-on-read',
                   >     'write.update.mode'='merge-on-read'
                   > );
25/04/30 20:01:48 WARN BaseTransaction: Failed to load metadata for a committed snapshot, skipping clean-up
Response code
Time taken: 0.767 seconds

spark-sql (default)> describe formatted wmf_content.mediawiki_content_current_v1;
....
Table Properties	[current-snapshot-id=8649756776621205924,format=iceberg/parquet,write.delete.mode=merge-on-read,write.format.default=parquet,write.merge.mode=merge-on-read,write.metadata.delete-after-commit.enabled=true,write.target-file-size-bytes=134217728,write.update.mode=merge-on-read]	
Time taken: 0.091 seconds, Fetched 36 row(s)

wmf_content.mediawiki_content_history_v1:

spark-sql (default)> ALTER TABLE wmf_content.mediawiki_content_history_v1 SET TBLPROPERTIES (
                   >     'write.delete.mode'='merge-on-read',
                   >     'write.merge.mode'='merge-on-read',
                   >     'write.update.mode'='merge-on-read'
                   > );
25/04/30 20:03:17 WARN BaseTransaction: Failed to load metadata for a committed snapshot, skipping clean-up
Response code
Time taken: 1.125 seconds

spark-sql (default)> ALTER TABLE wmf_content.mediawiki_content_history_v1 UNSET TBLPROPERTIES ('write.metadata.previous-versions-max');
25/04/30 20:03:31 WARN BaseTransaction: Failed to load metadata for a committed snapshot, skipping clean-up
Response code
Time taken: 0.204 seconds

spark-sql (default)> describe formatted wmf_content.mediawiki_content_history_v1;
...
Table Properties	[current-snapshot-id=8044191397808821822,format=iceberg/parquet,sort-order=wiki_id ASC NULLS FIRST, page_id ASC NULLS FIRST, revision_dt ASC NULLS FIRST,write.delete.mode=merge-on-read,write.distribution-mode=range,write.format.default=parquet,write.merge.mode=merge-on-read,write.metadata.delete-after-commit.enabled=true,write.target-file-size-bytes=134217728,write.update.mode=merge-on-read]	
Time taken: 0.369 seconds, Fetched 38 row(s)

Mentioned in SAL (#wikimedia-analytics) [2025-04-30T20:07:41Z] <xcollazo> Enabled merge-on-read on mw-content-history table and other TBLPROPERTIES changes. T393012.

merge-on-read is now enabled on wmf_content.mediawiki_content_history_v1.

Will monitor tomorrows run, super exited for the (potential) perf gains!

For a couple consecutive days now, we have received alerts regarding the HdfsRpcQueueLength metric, suggesting HDFS is under stress at times.

We enabled merge-on-read on this table on Apr 30, for it to run for the first time on May 1. The start of the HDFS stress alerts concurs with this general timeline.

More specifically, the most recent HDFS stress window, as measured by the "Current length of the call queue" metric (same metric used for the alerts), happened from:
Started 2025-05-06 02:45:00 UTC
Ended 2025-05-06 09:30:00 UTC

This concurs with the runtimes of:

The spark_process_events job of the mw_content_merge_events_to_mw_content_history_daily DAG
Started 2025-05-06, 02:32:36 UTC
Ended 2025-05-06, 05:04:23 UTC

The spark_process_changes job of the mw_content_merge_changes_to_mw_content_current_daily DAG
Started 2025-05-06, 05:26:31 UTC
Ended 2025-05-06, 09:31:57 UTC

The interesting thing is that it seems like the total amount of delete files is low compared to all data files. Below, content = 0 is a data file, while content = 1 is a delete file:

spark.sql("""
SELECT count(1) as count, content
FROM wmf_content.mediawiki_content_history_v1.files
GROUP BY content
""").show(20, truncate=False)
[Stage 32:===========================================>              (6 + 2) / 8]
+------+-------+
|count |content|
+------+-------+
|5318  |1      |
|135619|0      |
+------+-------+

However the total deletes (i.e the pointers inside the delete files) are significant:

spark.sql("""
SELECT count(1) as count
FROM wmf_content.mediawiki_content_history_v1.position_deletes
""").show(20, truncate=False)
[Stage 31:>                                                         (0 + 1) / 1]
+-------+
|count  |
+-------+
|1110118|
+-------+


spark.sql("""
SELECT count(1) as count, partition
FROM wmf_content.mediawiki_content_history_v1.position_deletes
GROUP BY partition
ORDER BY count DESC
""").show(20, truncate=False)
[Stage 20:=====================================================>(165 + 2) / 167]
+------+--------------+
|count |partition     |
+------+--------------+
|377118|{enwiki}      |
|306651|{commonswiki} |
|29441 |{frwiki}      |
|29079 |{eswiki}      |
|27853 |{arwiki}      |
|26705 |{zhwiki}      |
|26042 |{ruwiki}      |
|22574 |{itwiki}      |
|22337 |{dewiki}      |
|16615 |{wikidatawiki}|
|16354 |{jawiki}      |
|15948 |{fawiki}      |
|14942 |{ptwiki}      |
|11571 |{idwiki}      |
|8688  |{kowiki}      |
|8088  |{cawiki}      |
|8050  |{plwiki}      |
|8017  |{ukwiki}      |
|7811  |{trwiki}      |
|7704  |{hewiki}      |
+------+--------------+
only showing top 20 rows

Having said that, it is clear that for our downstream use cases of reading this table fully, the amount of deletes is too much, generating way more splits to read the table fully, which in turn also make jobs take longer downstream.

Before disabling merge-on-read though, I want to try a more aggressive table maintenance: from weekly, to daily. Such a strategy can still benefit us file size wise if we consider that each day we do 7 commits to this table.

xcollazo opened https://gitlab.wikimedia.org/repos/data-engineering/airflow-dags/-/merge_requests/1287

analytics: do daily instead of weekly table maintenance on mw-content-history table.

xcollazo merged https://gitlab.wikimedia.org/repos/data-engineering/airflow-dags/-/merge_requests/1287

analytics: do daily instead of weekly table maintenance on mw-content-history table.

Total deletes now in a much better position compared to T393012#10797777:

spark.sql("""
SELECT count(1) as count
FROM wmf_content.mediawiki_content_history_v1.position_deletes
""").show(20, truncate=False)
spark.sql("""
SELECT count(1) as count
FROM wmf_content.mediawiki_content_history_v1.position_deletes
""").show(20, truncate=False)
[Stage 48:====================================================>   (32 + 2) / 34]
+------+
|count |
+------+
|198395|
+------+
spark.sql("""
SELECT count(1) as count, partition
FROM wmf_content.mediawiki_content_history_v1.position_deletes
GROUP BY partition
ORDER BY count DESC
""").show(20, truncate=False)
[Stage 51:====================================================>   (32 + 2) / 34]
+-----+------------------+
|count|partition         |
+-----+------------------+
|41562|{enwiki}          |
|23494|{commonswiki}     |
|9174 |{frwiki}          |
|4720 |{testwikidatawiki}|
|3863 |{zhwiki}          |
|3597 |{cebwiki}         |
|3440 |{bnwiki}          |
|3317 |{mrwiki}          |
|3272 |{enwikisource}    |
|3179 |{huwikibooks}     |
|3009 |{itwiki}          |
|2896 |{eswiki}          |
|2834 |{simplewiki}      |
|2678 |{frwikisource}    |
|2671 |{fawiki}          |
|2650 |{specieswiki}     |
|2441 |{dewiki}          |
|2404 |{urwiki}          |
|2325 |{ruwiki}          |
|2210 |{jawiki}          |
+-----+------------------+
only showing top 20 rows

Some evidence of HDFS stress remains. although significantly less than other periods.

I think we should give this daily maintenance strategy a couple more runs to see if it stabilizes.

I also think we should tune HDFS's dfs.namenode.handler.count from current 127 to double that, perhaps to 256 (link), but let's wait a bit before attempting that. Perhaps on its own ticket.

@xcollazo regarding the suggested increase of dfs.namenode.handler.count, what's the name nodes load in these crunch cases?

@xcollazo regarding the suggested increase of dfs.namenode.handler.count, what's the name nodes load in these crunch cases?

an-master1004, the current namenode, was at ~20% CPU usage, nominal RAM usage, nominal disk usage. Here is the Grafana for the event discussed in T393012#10797481: https://grafana.wikimedia.org/goto/Ooc5gwbHg?orgId=1.

This is why I think that dfs.namenode.handler.count is low considering the available resources, especially CPU.

set "spark.sql.iceberg.locality.enabled":"true" on wmf_content.mediawiki_content_current_v1 ingest.

set "spark.sql.iceberg.locality.enabled":"true" on wmf_content.mediawiki_content_current_v1 ingest.

This did not help at all. Reverting to false.

Ok, even though there are more avenues to explore, like tuning the HDFS Namenode further, or going deep into Iceberg to figure why we produce so many FileInfo requests, I am going to throw the towel for now and revert table wmf_content.mediawiki_content_history_v1 from merge-on-read to copy-on-write.

This means the runtime of mw_content_merge_events_to_mw_content_history_daily DAG will go from the current ~45 mins back to ~8 hours.

Not ideal, but this issue has been a big time sink and we need to move on to close on T391279: Daily updated wmf_content.mediawiki_content_current_v1.

Ran the following:

$ hostname -f
an-launcher1002.eqiad.wmnet

$ sudo -u analytics bash

$ kerberos-run-command analytics spark3-sql

spark-sql (default)> ALTER TABLE wmf_content.mediawiki_content_history_v1 SET TBLPROPERTIES (
                   >     'write.delete.mode'='copy-on-write',
                   >     'write.merge.mode'='copy-on-write',
                   >     'write.update.mode'='copy-on-write'
                   > );
25/05/15 16:41:22 WARN BaseTransaction: Failed to load metadata for a committed snapshot, skipping clean-up
Response code
Time taken: 5.757 seconds

Still to do here:

  • Run daily maintenance for a couple days to clear up remaining delete files
  • Move maintenance from daily back to weekly.

Still to do here:

  • Run daily maintenance for a couple days to clear up remaining delete files
  • Move maintenance from daily back to weekly.

I think we are good with this experiment now.