Database cells are rows, not columns
A user who adds a property expects it to work immediately, and a relational table cannot gain a column per user without a migration per user. Entity-attribute-value is the standard answer: one row per cell in row_property_values, typed by the property it points at. What it costs is everything a real column gives — the database cannot enforce the type, cannot index a property usefully without a composite index, and reading one row of a 20-property database is 20 rows that have to be pivoted in the application. It is the right trade only because the alternative is not available.