Repository: cortexproject/cortex
Pull request #2186 (2020-02-25 22:36:51)

Generate blocks storage config file doc
**What this PR does**:
Currently, the blocks storage config documented in `docs/operations/blocks-storage.md` was manually generated. This is error prone and drifted a bit.

In this PR I'm introducing the templating support to the doc generator tool, switching the config file reference specified in `docs/operations/blocks-storage.md` to the auto-generated one as well.

Notes:
- Blocks storage config has been added to the config file reference doc too
- The changes to the doc generator tool have been made to try to keep the changeset small, but some refactoring would be probably required at some point

_Please enable "hide whitespace changes" when reviewing this._

**Which issue(s) this PR fixes**:
N/A

**Checklist**
- [ ] Tests updated
- [x] Documentation added
- [ ] `CHANGELOG.md` updated - the order of entries should be `[CHANGE]`, `[FEATURE]`, `[ENHANCEMENT]`, `[BUGFIX]`



Patch:
diff --git a/Makefile b/Makefile
index 0c3cfd01c4..583802e452 100644
--- a/Makefile
+++ b/Makefile
@@ -194,15 +194,15 @@ prime-minikube: save-images
 	done
 
 # Generates the config file documentation.
-doc:
-	cp ./docs/configuration/config-file-reference.template ./docs/configuration/config-file-reference.md
-	go run ./tools/doc-generator/ >> ./docs/configuration/config-file-reference.md
+doc: clean-doc
+	go run ./tools/doc-generator ./docs/configuration/config-file-reference.template >> ./docs/configuration/config-file-reference.md
+	go run ./tools/doc-generator ./docs/operations/blocks-storage.template           >> ./docs/operations/blocks-storage.md
 
 clean-doc:
-	rm -f ./docs/configuration/config-file-reference.md
+	rm -f ./docs/configuration/config-file-reference.md ./docs/operations/blocks-storage.md
 
-check-doc: clean-doc doc
-	@git diff --exit-code -- ./docs/configuration/config-file-reference.md
+check-doc: doc
+	@git diff --exit-code -- ./docs/configuration/config-file-reference.md ./docs/operations/blocks-storage.md
 
 web-serve:
 	cd website && hugo --config config.toml -v server
diff --git a/docs/configuration/config-file-reference.md b/docs/configuration/config-file-reference.md
index d9e9493109..5a5d7cbb55 100644
--- a/docs/configuration/config-file-reference.md
+++ b/docs/configuration/config-file-reference.md
@@ -105,6 +105,13 @@ Where default_value is the value to use if the environment variable is undefined
 # The table_manager_config configures the Cortex table-manager.
 [table_manager: <table_manager_config>]
 
+# The tsdb_config configures the experimental blocks storage.
+[tsdb: <tsdb_config>]
+
+# The compactor_config configures the compactor for the experimental blocks
+# storage.
+[compactor: <compactor_config>]
+
 # The ruler_config configures the Cortex ruler.
 [ruler: <ruler_config>]
 
@@ -128,7 +135,7 @@ runtime_config:
 [memberlist: <memberlist_config>]
 ```
 
-## `server_config`
+### `server_config`
 
 The `server_config` configures the HTTP and gRPC server of the launched service(s).
 
@@ -224,7 +231,7 @@ The `server_config` configures the HTTP and gRPC server of the launched service(
 [http_path_prefix: <string> | default = ""]
 ```
 
-## `distributor_config`
+### `distributor_config`
 
 The `distributor_config` configures the Cortex distributor.
 
@@ -358,7 +365,7 @@ ring:
   [heartbeat_timeout: <duration> | default = 1m0s]
 ```
 
-## `ingester_config`
+### `ingester_config`
 
 The `ingester_config` configures the Cortex ingester.
 
@@ -511,7 +518,7 @@ lifecycler:
 [rateupdateperiod: <duration> | default = 15s]
 ```
 
-## `querier_config`
+### `querier_config`
 
 The `querier_config` configures the Cortex querier.
 
@@ -564,7 +571,7 @@ The `querier_config` configures the Cortex querier.
 [active_query_tracker_dir: <string> | default = "./active-query-tracker"]
 ```
 
-## `query_frontend_config`
+### `query_frontend_config`
 
 The `query_frontend_config` configures the Cortex query-frontend.
 
@@ -587,7 +594,7 @@ The `query_frontend_config` configures the Cortex query-frontend.
 [log_queries_longer_than: <duration> | default = 0s]
 ```
 
-## `queryrange_config`
+### `queryrange_config`
 
 The `queryrange_config` configures the query splitting and caching in the Cortex query-frontend.
 
@@ -664,7 +671,7 @@ results_cache:
 [parallelise_shardable_queries: <boolean> | default = false]
 ```
 
-## `ruler_config`
+### `ruler_config`
 
 The `ruler_config` configures the Cortex ruler.
 
@@ -785,7 +792,7 @@ ring:
 [enable_api: <boolean> | default = false]
 ```
 
-## `alertmanager_config`
+### `alertmanager_config`
 
 The `alertmanager_config` configures the Cortex alertmanager.
 
@@ -851,7 +858,7 @@ store:
     [path: <string> | default = ""]
 ```
 
-## `table_manager_config`
+### `table_manager_config`
 
 The `table_manager_config` configures the Cortex table-manager.
 
@@ -1181,7 +1188,7 @@ chunk_tables_provisioning:
   [inactive_read_scale_lastn: <int> | default = 4]
 ```
 
-## `storage_config`
+### `storage_config`
 
 The `storage_config` configures where Cortex stores the data (chunks storage engine).
 
@@ -1518,7 +1525,7 @@ index_queries_cache_config:
   [fifocache: <fifo_cache_config>]
 ```
 
-## `chunk_store_config`
+### `chunk_store_config`
 
 The `chunk_store_config` configures how Cortex stores the data (chunks storage engine).
 
@@ -1606,7 +1613,7 @@ write_dedupe_cache_config:
 [max_look_back_period: <duration> | default = 0s]
 ```
 
-## `ingester_client_config`
+### `ingester_client_config`
 
 The `ingester_client_config` configures how the Cortex distributors connect to the ingesters.
 
@@ -1650,7 +1657,7 @@ grpc_client_config:
     [maxretries: <int> | default = 10]
 ```
 
-## `frontend_worker_config`
+### `frontend_worker_config`
 
 The `frontend_worker_config` configures the worker - running within the Cortex querier - picking up and executing queries enqueued by the query-frontend.
 
@@ -1706,7 +1713,7 @@ grpc_client_config:
     [maxretries: <int> | default = 10]
 ```
 
-## `etcd_config`
+### `etcd_config`
 
 The `etcd_config` configures the etcd client.
 
@@ -1724,7 +1731,7 @@ The `etcd_config` configures the etcd client.
 [max_retries: <int> | default = 10]
 ```
 
-## `consul_config`
+### `consul_config`
 
 The `consul_config` configures the consul client.
 
@@ -1755,7 +1762,7 @@ The `consul_config` configures the consul client.
 [watchkeyburstsize: <int> | default = 1]
 ```
 
-## `memberlist_config`
+### `memberlist_config`
 
 The `memberlist_config` configures the Gossip memberlist.
 
@@ -1830,7 +1837,7 @@ The `memberlist_config` configures the Gossip memberlist.
 [packet_write_timeout: <duration> | default = 5s]
 ```
 
-## `limits_config`
+### `limits_config`
 
 The `limits_config` configures default and per-tenant limits imposed by Cortex services (ie. distributor, ingester, ...).
 
@@ -1960,7 +1967,7 @@ The `limits_config` configures default and per-tenant limits imposed by Cortex s
 [per_tenant_override_period: <duration> | default = 10s]
 ```
 
-## `redis_config`
+### `redis_config`
 
 The `redis_config` configures the Redis backend cache.
 
@@ -1995,7 +2002,7 @@ The `redis_config` configures the Redis backend cache.
 [enable_tls: <boolean> | default = false]
 ```
 
-## `memcached_config`
+### `memcached_config`
 
 The `memcached_config` block configures how data is stored in Memcached (ie. expiration).
 
@@ -2013,7 +2020,7 @@ The `memcached_config` block configures how data is stored in Memcached (ie. exp
 [parallelism: <int> | default = 100]
 ```
 
-## `memcached_client_config`
+### `memcached_client_config`
 
 The `memcached_client_config` configures the client used to connect to Memcached.
 
@@ -2044,7 +2051,7 @@ The `memcached_client_config` configures the client used to connect to Memcached
 [consistent_hash: <boolean> | default = false]
 ```
 
-## `fifo_cache_config`
+### `fifo_cache_config`
 
 The `fifo_cache_config` configures the local in-memory cache.
 
@@ -2058,7 +2065,7 @@ The `fifo_cache_config` configures the local in-memory cache.
 [validity: <duration> | default = 0s]
 ```
 
-## `configdb_config`
+### `configdb_config`
 
 The `configdb_config` configures the config database storing rules and alerts, and used by the 'configs' service to expose APIs to manage them.
 
@@ -2076,7 +2083,7 @@ The `configdb_config` configures the config database storing rules and alerts, a
 [passwordfile: <string> | default = ""]
 ```
 
-## `configstore_config`
+### `configstore_config`
 
 The `configstore_config` configures the config database storing rules and alerts, and is used by the Cortex alertmanager.
 
@@ -2089,3 +2096,239 @@ The `configstore_config` configures the config database storing rules and alerts
 # CLI flag: -<prefix>.configs.client-timeout
 [clienttimeout: <duration> | default = 5s]
 ```
+
+### `tsdb_config`
+
+The `tsdb_config` configures the experimental blocks storage.
+
+```yaml
+# Local directory to store TSDBs in the ingesters.
+# CLI flag: -experimental.tsdb.dir
+[dir: <string> | default = "tsdb"]
+
+# TSDB blocks range period.
+# CLI flag: -experimental.tsdb.block-ranges-period
+[block_ranges_period: <list of duration> | default = 2h0m0s]
+
+# TSDB blocks retention in the ingester before a block is removed. This should
+# be larger than the block_ranges_period and large enough to give ingesters
+# enough time to discover newly uploaded blocks.
+# CLI flag: -experimental.tsdb.retention-period
+[retention_period: <duration> | default = 6h0m0s]
+
+# How frequently the TSDB blocks are scanned and new ones are shipped to the
+# storage. 0 means shipping is disabled.
+# CLI flag: -experimental.tsdb.ship-interval
+[ship_interval: <duration> | default = 1m0s]
+
+# Maximum number of tenants concurrently shipping blocks to the storage.
+# CLI flag: -experimental.tsdb.ship-concurrency
+[ship_concurrency: <int> | default = 10]
+
+# Backend storage to use. Either "s3" or "gcs".
+# CLI flag: -experimental.tsdb.backend
+[backend: <string> | default = "s3"]
+
+bucket_store:
+  # Directory to store synchronized TSDB index headers.
+  # CLI flag: -experimental.tsdb.bucket-store.sync-dir
+  [sync_dir: <string> | default = "tsdb-sync"]
+
+  # How frequently scan the bucket to look for changes (new blocks shipped by
+  # ingesters and blocks removed by retention or compaction). 0 disables it.
+  # CLI flag: -experimental.tsdb.bucket-store.sync-interval
+  [sync_interval: <duration> | default = 5m0s]
+
+  # Size - in bytes - of a per-tenant in-memory index cache used to speed up
+  # blocks index lookups.
+  # CLI flag: -experimental.tsdb.bucket-store.index-cache-size-bytes
+  [index_cache_size_bytes: <int> | default = 262144000]
+
+  # Max size - in bytes - of a per-tenant chunk pool, used to reduce memory
+  # allocations.
+  # CLI flag: -experimental.tsdb.bucket-store.max-chunk-pool-bytes
+  [max_chunk_pool_bytes: <int> | default = 2147483648]
+
+  # Max number of samples per query when loading series from the long-term
+  # storage. 0 disables the limit.
+  # CLI flag: -experimental.tsdb.bucket-store.max-sample-count
+  [max_sample_count: <int> | default = 0]
+
+  # Max number of concurrent queries to execute against the long-term storage on
+  # a per-tenant basis.
+  # CLI flag: -experimental.tsdb.bucket-store.max-concurrent
+  [max_concurrent: <int> | default = 20]
+
+  # Maximum number of concurrent tenants synching blocks.
+  # CLI flag: -experimental.tsdb.bucket-store.tenant-sync-concurrency
+  [tenant_sync_concurrency: <int> | default = 10]
+
+  # Maximum number of concurrent blocks synching per tenant.
+  # CLI flag: -experimental.tsdb.bucket-store.block-sync-concurrency
+  [block_sync_concurrency: <int> | default = 20]
+
+  # Number of Go routines to use when syncing block meta files from object
+  # storage per tenant.
+  # CLI flag: -experimental.tsdb.bucket-store.meta-sync-concurrency
+  [meta_sync_concurrency: <int> | default = 20]
+
+# How frequently does Cortex try to compact TSDB head. Block is only created if
+# data covers smallest block range. Must be greater than 0 and max 5 minutes.
+# CLI flag: -experimental.tsdb.head-compaction-interval
+[head_compaction_interval: <duration> | default = 1m0s]
+
+# Maximum number of tenants concurrently compacting TSDB head into a new block
+# CLI flag: -experimental.tsdb.head-compaction-concurrency
+[head_compaction_concurrency: <int> | default = 5]
+
+# limit the number of concurrently opening TSDB's on startup
+# CLI flag: -experimental.tsdb.max-tsdb-opening-concurrency-on-startup
+[max_tsdb_opening_concurrency_on_startup: <int> | default = 10]
+
+s3:
+  # S3 endpoint without schema
+  # CLI flag: -experimental.tsdb.s3.endpoint
+  [endpoint: <string> | default = ""]
+
+  # S3 bucket name
+  # CLI flag: -experimental.tsdb.s3.bucket-name
+  [bucket_name: <string> | default = ""]
+
+  # S3 secret access key
+  # CLI flag: -experimental.tsdb.s3.secret-access-key
+  [secret_access_key: <string> | default = ""]
+
+  # S3 access key ID
+  # CLI flag: -experimental.tsdb.s3.access-key-id
+  [access_key_id: <string> | default = ""]
+
+  # If enabled, use http:// for the S3 endpoint instead of https://. This could
+  # be useful in local dev/test environments while using an S3-compatible
+  # backend storage, like Minio.
+  # CLI flag: -experimental.tsdb.s3.insecure
+  [insecure: <boolean> | default = false]
+
+gcs:
+  # GCS bucket name
+  # CLI flag: -experimental.tsdb.gcs.bucket-name
+  [bucket_name: <string> | default = ""]
+
+  # JSON representing either a Google Developers Console client_credentials.json
+  # file or a Google Developers service account key file. If empty, fallback to
+  # Google default logic.
+  # CLI flag: -experimental.tsdb.gcs.service-account
+  [service_account: <string> | default = ""]
+
+azure:
+  # Azure storage account name
+  # CLI flag: -experimental.tsdb.azure.account-name
+  [account_name: <string> | default = ""]
+
+  # Azure storage account key
+  # CLI flag: -experimental.tsdb.azure.account-key
+  [account_key: <string> | default = ""]
+
+  # Azure storage container name
+  # CLI flag: -experimental.tsdb.azure.container-name
+  [container_name: <string> | default = ""]
+
+  # Azure storage endpoint suffix without schema. The account name will be
+  # prefixed to this value to create the FQDN
+  # CLI flag: -experimental.tsdb.azure.endpoint-suffix
+  [endpoint_suffix: <string> | default = ""]
+
+  # Number of retries for recoverable errors
+  # CLI flag: -experimental.tsdb.azure.max-retries
+  [max_retries: <int> | default = 20]
+```
+
+### `compactor_config`
+
+The `compactor_config` configures the compactor for the experimental blocks storage.
+
+```yaml
+# List of compaction time ranges.
+# CLI flag: -compactor.block-ranges
+[block_ranges: <list of duration> | default = 2h0m0s,12h0m0s,24h0m0s]
+
+# Number of Go routines to use when syncing block index and chunks files from
+# the long term storage.
+# CLI flag: -compactor.block-sync-concurrency
+[block_sync_concurrency: <int> | default = 20]
+
+# Number of Go routines to use when syncing block meta files from the long term
+# storage.
+# CLI flag: -compactor.meta-sync-concurrency
+[meta_sync_concurrency: <int> | default = 20]
+
+# Minimum age of fresh (non-compacted) blocks before they are being processed.
+# Malformed blocks older than the maximum of consistency-delay and 48h0m0s will
+# be removed.
+# CLI flag: -compactor.consistency-delay
+[consistency_delay: <duration> | default = 30m0s]
+
+# Data directory in which to cache blocks and process compactions
+# CLI flag: -compactor.data-dir
+[data_dir: <string> | default = "./data"]
+
+# The frequency at which the compaction runs
+# CLI flag: -compactor.compaction-interval
+[compaction_interval: <duration> | default = 1h0m0s]
+
+# How many times to retry a failed compaction during a single compaction
+# interval
+# CLI flag: -compactor.compaction-retries
+[compaction_retries: <int> | default = 3]
+
+# Shard tenants across multiple compactor instances. Sharding is required if you
+# run multiple compactor instances, in order to coordinate compactions and avoid
+# race conditions leading to the same tenant blocks simultaneously compacted by
+# different instances.
+# CLI flag: -compactor.sharding-enabled
+[sharding_enabled: <boolean> | default = false]
+
+sharding_ring:
+  kvstore:
+    # Backend storage to use for the ring. Supported values are: consul, etcd,
+    # inmemory, multi, memberlist (experimental).
+    # CLI flag: -compactor.ring.store
+    [store: <string> | default = "consul"]
+
+    # The prefix for the keys in the store. Should end with a /.
+    # CLI flag: -compactor.ring.prefix
+    [prefix: <string> | default = "collectors/"]
+
+    # The consul_config configures the consul client.
+    # The CLI flags prefix for this block config is: compactor.ring
+    [consul: <consul_config>]
+
+    # The etcd_config configures the etcd client.
+    # The CLI flags prefix for this block config is: compactor.ring
+    [etcd: <etcd_config>]
+
+    multi:
+      # Primary backend storage used by multi-client.
+      # CLI flag: -compactor.ring.multi.primary
+      [primary: <string> | default = ""]
+
+      # Secondary backend storage used by multi-client.
+      # CLI flag: -compactor.ring.multi.secondary
+      [secondary: <string> | default = ""]
+
+      # Mirror writes to secondary store.
+      # CLI flag: -compactor.ring.multi.mirror-enabled
+      [mirror_enabled: <boolean> | default = false]
+
+      # Timeout for storing value to secondary store.
+      # CLI flag: -compactor.ring.multi.mirror-timeout
+      [mirror_timeout: <duration> | default = 2s]
+
+  # Period at which to heartbeat to the ring.
+  # CLI flag: -compactor.ring.heartbeat-period
+  [heartbeat_period: <duration> | default = 5s]
+
+  # The heartbeat timeout after which compactors are considered unhealthy within
+  # the ring.
+  # CLI flag: -compactor.ring.heartbeat-timeout
+  [heartbeat_timeout: <duration> | default = 1m0s]
+```
diff --git a/docs/configuration/config-file-reference.template b/docs/configuration/config-file-reference.template
index 95d8408b88..f863efb63d 100644
--- a/docs/configuration/config-file-reference.template
+++ b/docs/configuration/config-file-reference.template
@@ -48,3 +48,4 @@ Where default_value is the value to use if the environment variable is undefined
 
 ### Supported contents and default values of the config file
 
+{{ .ConfigFile }}
diff --git a/docs/operations/blocks-storage.md b/docs/operations/blocks-storage.md
index f8970712eb..5b36c001d0 100644
--- a/docs/operations/blocks-storage.md
+++ b/docs/operations/blocks-storage.md
@@ -81,7 +81,7 @@ The general [configuration documentation](../configuration/_index.md) also appli
 
 ### `storage_config`
 
-The `storage_config` block configures the storage engine.
+The `storage_config` configures the storage engine.
 
 ```yaml
 storage:
@@ -92,62 +92,51 @@ storage:
 
 ### `tsdb_config`
 
-The `tsdb_config` block configures the blocks storage engine (based on TSDB).
+The `tsdb_config` configures the experimental blocks storage.
 
 ```yaml
 tsdb:
-  # Backend storage to use. Either "s3" or "gcs".
-  # CLI flag: -experimental.tsdb.backend
-  backend: <string>
-
   # Local directory to store TSDBs in the ingesters.
   # CLI flag: -experimental.tsdb.dir
   [dir: <string> | default = "tsdb"]
 
   # TSDB blocks range period.
   # CLI flag: -experimental.tsdb.block-ranges-period
-  [ block_ranges_period: <list of duration> | default = [2h]]
+  [block_ranges_period: <list of duration> | default = 2h0m0s]
 
-  # TSDB blocks retention in the ingester before a block is removed. This
-  # should be larger than the block_ranges_period and large enough to give
-  # ingesters enough time to discover newly uploaded blocks.
+  # TSDB blocks retention in the ingester before a block is removed. This should
+  # be larger than the block_ranges_period and large enough to give ingesters
+  # enough time to discover newly uploaded blocks.
   # CLI flag: -experimental.tsdb.retention-period
-  [ retention_period: <duration> | default = 6h]
+  [retention_period: <duration> | default = 6h0m0s]
 
-  # How frequently the TSDB blocks are scanned and new ones are shipped to
-  # the storage. 0 means shipping is disabled.
+  # How frequently the TSDB blocks are scanned and new ones are shipped to the
+  # storage. 0 means shipping is disabled.
   # CLI flag: -experimental.tsdb.ship-interval
-  [ship_interval: <duration> | default = 1m]
+  [ship_interval: <duration> | default = 1m0s]
 
   # Maximum number of tenants concurrently shipping blocks to the storage.
   # CLI flag: -experimental.tsdb.ship-concurrency
   [ship_concurrency: <int> | default = 10]
 
-  # How frequently does Cortex try to compact TSDB head. Block is only created if data covers smallest block range.
-  # Must be greater than 0 and max 5 minutes.
-  # CLI flag: -experimental.tsdb.head-compaction-interval
-  [head_compaction_interval: <duration> | default = 1m]
-
-  # Maximum number of tenants concurrently compacting TSDB head into a new block.
-  # CLI flag: -experimental.tsdb.head-compaction-concurrency
-  [head_compaction_concurrency: <int> | default = 5]
+  # Backend storage to use. Either "s3" or "gcs".
+  # CLI flag: -experimental.tsdb.backend
+  [backend: <string> | default = "s3"]
 
-  # The bucket store configuration applies to queriers and configure how queriers
-  # iteract with the long-term storage backend.
   bucket_store:
-    # Directory to store synched TSDB index headers.
+    # Directory to store synchronized TSDB index headers.
     # CLI flag: -experimental.tsdb.bucket-store.sync-dir
     [sync_dir: <string> | default = "tsdb-sync"]
 
-    # How frequently scan the bucket to look for changes (new blocks shipped by
-    # ingesters and blocks removed by retention or compaction).
+    # How frequently scan the bucket to look for changes (new blocks shipped by
+    # ingesters and blocks removed by retention or compaction). 0 disables it.
     # CLI flag: -experimental.tsdb.bucket-store.sync-interval
-    [sync_interval: <duration> | default = 5m]
+    [sync_interval: <duration> | default = 5m0s]
 
-    # Size - in bytes - of a per-tenant in-memory index cache used to speed up
+    # Size - in bytes - of a per-tenant in-memory index cache used to speed up
     # blocks index lookups.
     # CLI flag: -experimental.tsdb.bucket-store.index-cache-size-bytes
-    [ index_cache_size_bytes: <int> | default = 262144000]
+    [index_cache_size_bytes: <int> | default = 262144000]
 
     # Max size - in bytes - of a per-tenant chunk pool, used to reduce memory
     # allocations.
@@ -172,162 +161,171 @@ tsdb:
     # CLI flag: -experimental.tsdb.bucket-store.block-sync-concurrency
     [block_sync_concurrency: <int> | default = 20]
 
-  # Configures the S3 storage backend.
-  # Required only when "s3" backend has been selected.
-  s3:
-    # S3 bucket name.
-    # CLI flag: -experimental.tsdb.s3.bucket-name string
-    bucket_name: <string>
+    # Number of Go routines to use when syncing block meta files from object
+    # storage per tenant.
+    # CLI flag: -experimental.tsdb.bucket-store.meta-sync-concurrency
+    [meta_sync_concurrency: <int> | default = 20]
 
-    # S3 access key ID. If empty, fallbacks to AWS SDK default logic.
-    # CLI flag: -experimental.tsdb.s3.access-key-id string
-    [access_key_id: <string>]
+  # How frequently does Cortex try to compact TSDB head. Block is only created
+  # if data covers smallest block range. Must be greater than 0 and max 5
+  # minutes.
+  # CLI flag: -experimental.tsdb.head-compaction-interval
+  [head_compaction_interval: <duration> | default = 1m0s]
+
+  # Maximum number of tenants concurrently compacting TSDB head into a new block
+  # CLI flag: -experimental.tsdb.head-compaction-concurrency
+  [head_compaction_concurrency: <int> | default = 5]
 
-    # S3 secret access key. If empty, fallbacks to AWS SDK default logic.
-    # CLI flag: -experimental.tsdb.s3.secret-access-key string
-    [secret_access_key: <string>]
+  # limit the number of concurrently opening TSDB's on startup
+  # CLI flag: -experimental.tsdb.max-tsdb-opening-concurrency-on-startup
+  [max_tsdb_opening_concurrency_on_startup: <int> | default = 10]
 
-    # S3 endpoint without schema. By defaults it use the AWS S3 endpoint.
+  s3:
+    # S3 endpoint without schema
     # CLI flag: -experimental.tsdb.s3.endpoint
     [endpoint: <string> | default = ""]
 
-    # If enabled, use http:// for the S3 endpoint instead of https://.
-    # This could be useful in local dev/test environments while using
-    # an S3-compatible backend storage, like Minio.
+    # S3 bucket name
+    # CLI flag: -experimental.tsdb.s3.bucket-name
+    [bucket_name: <string> | default = ""]
+
+    # S3 secret access key
+    # CLI flag: -experimental.tsdb.s3.secret-access-key
+    [secret_access_key: <string> | default = ""]
+
+    # S3 access key ID
+    # CLI flag: -experimental.tsdb.s3.access-key-id
+    [access_key_id: <string> | default = ""]
+
+    # If enabled, use http:// for the S3 endpoint instead of https://. This
+    # could be useful in local dev/test environments while using an
+    # S3-compatible backend storage, like Minio.
     # CLI flag: -experimental.tsdb.s3.insecure
     [insecure: <boolean> | default = false]
 
-  # Configures the GCS storage backend.
-  # Required only when "gcs" backend has been selected.
   gcs:
-    # GCS bucket name.
+    # GCS bucket name
     # CLI flag: -experimental.tsdb.gcs.bucket-name
-    bucket_name: <string>
+    [bucket_name: <string> | default = ""]
 
-    # JSON representing either a Google Developers Console client_credentials.json
-    # file or a Google Developers service account key file. If empty, fallbacks to
-    # Google SDK default logic.
-    # CLI flag: -experimental.tsdb.gcs.service-account string
-    [ service_account: <string>]
+    # JSON representing either a Google Developers Console
+    # client_credentials.json file or a Google Developers service account key
+    # file. If empty, fallback to Google default logic.
+    # CLI flag: -experimental.tsdb.gcs.service-account
+    [service_account: <string> | default = ""]
 
-  # Configures the Azure storage backend
-  # Required only when "azure" backend has been selected.
   azure:
     # Azure storage account name
     # CLI flag: -experimental.tsdb.azure.account-name
-    account_name: <string>
+    [account_name: <string> | default = ""]
+
     # Azure storage account key
     # CLI flag: -experimental.tsdb.azure.account-key
-    account_key: <string>
+    [account_key: <string> | default = ""]
+
     # Azure storage container name
     # CLI flag: -experimental.tsdb.azure.container-name
-    container_name: <string>
-    # Azure storage endpoint suffix without schema.
-    # The account name will be prefixed to this value to create the FQDN
+    [container_name: <string> | default = ""]
+
+    # Azure storage endpoint suffix without schema. The account name will be
+    # prefixed to this value to create the FQDN
     # CLI flag: -experimental.tsdb.azure.endpoint-suffix
-    endpoint_suffix: <string>
+    [endpoint_suffix: <string> | default = ""]
+
     # Number of retries for recoverable errors
     # CLI flag: -experimental.tsdb.azure.max-retries
-    [ max_retries: <int> | default=20 ]
+    [max_retries: <int> | default = 20]
 ```
-
 ### `compactor_config`
 
-The `compactor_config` block configures the optional compactor service.
+The `compactor_config` configures the compactor for the experimental blocks storage.
 
 ```yaml
 compactor:
-    # List of compaction time ranges.
-    # CLI flag: -compactor.block-ranges
-    [block_ranges: <list of duration> | default = [2h,12h,24h]]
-
-    # Number of Go routines to use when syncing block index and chunks files
-    # from the long term storage.
-    # CLI flag: -compactor.block-sync-concurrency
-    [block_sync_concurrency: <int> | default = 20]
-
-    # Number of Go routines to use when syncing block meta files from the long
-    # term storage.
-    # CLI flag: -compactor.meta-sync-concurrency
-    [meta_sync_concurrency: <int> | default = 20]
-
-    # Minimum age of fresh (non-compacted) blocks before they are being processed,
-    # in order to skip blocks that are still uploading from ingesters. Malformed
-    # blocks older than the maximum of consistency-delay and 30m will be removed.
-    # CLI flag: -compactor.consistency-delay
-    [consistency_delay: <duration> | default = 30m]
-
-    # Directory in which to cache downloaded blocks to compact and to store the
-    # newly created block during the compaction process.
-    # CLI flag: -compactor.data-dir
-    [data_dir: <string> | default = "./data"]
-
-    # The frequency at which the compactor should look for new blocks eligible for
-    # compaction and trigger their compaction.
-    # CLI flag: -compactor.compaction-interval
-    [compaction_interval: <duration> | default = 1h]
-
-    # How many times to retry a failed compaction during a single compaction
-    # interval.
-    # CLI flag: -compactor.compaction-retries
-    [compaction_retries: <int> | default = 3]
-
-    # Shard tenants across multiple compactor instances. Sharding is required if
-    # you run multiple compactor instances, in order to coordinate compactions
-    # and avoid race conditions leading to the same tenant blocks simultaneously
-    # compacted by different instances.
-    # CLI flag: -compactor.sharding-enabled
-    [sharding_enabled: <bool> | default = false]
-
-    # Configures the ring used when sharding is enabled.
-    sharding_ring:
-      kvstore:
-        # Backend storage to use for the ring. Supported values are: consul, etcd,
-        # inmemory, multi, memberlist (experimental).
-        # CLI flag: -compactor.ring.store
-        [store: <string> | default = "consul"]
-
-        # The prefix for the keys in the store. Should end with a /.
-        # CLI flag: -compactor.ring.prefix
-        [prefix: <string> | default = "collectors/"]
-
-        # The consul_config configures the consul client.
-        # The CLI flags prefix for this block config is: compactor.ring
-        [consul: <consul_config>]
-
-        # The etcd_config configures the etcd client.
-        # The CLI flags prefix for this block config is: compactor.ring
-        [etcd: <etcd_config>]
-
-        # The memberlist_config configures the Gossip memberlist.
-        # The CLI flags prefix for this block config is: compactor.ring
-        [memberlist: <memberlist_config>]
-
-        multi:
-          # Primary backend storage used by multi-client.
-          # CLI flag: -compactor.ring.multi.primary
-          [primary: <string> | default = ""]
-
-          # Secondary backend storage used by multi-client.
-          # CLI flag: -compactor.ring.multi.secondary
-          [secondary: <string> | default = ""]
-
-          # Mirror writes to secondary store.
-          # CLI flag: -compactor.ring.multi.mirror-enabled
-          [mirror_enabled: <boolean> | default = false]
-
-          # Timeout for storing value to secondary store.
-          # CLI flag: -compactor.ring.multi.mirror-timeout
-          [mirror_timeout: <duration> | default = 2s]
-
-      # Period at which to heartbeat to the ring.
-      # CLI flag: -compactor.ring.heartbeat-period
-      [heartbeat_period: <duration> | default = 5m]
-
-      # The heartbeat timeout after which compactors are considered unhealthy
-      # within the ring.
-      # CLI flag: -compactor.ring.heartbeat-timeout
-      [heartbeat_timeout: <duration> | default = 1m]
+  # List of compaction time ranges.
+  # CLI flag: -compactor.block-ranges
+  [block_ranges: <list of duration> | default = 2h0m0s,12h0m0s,24h0m0s]
+
+  # Number of Go routines to use when syncing block index and chunks files from
+  # the long term storage.
+  # CLI flag: -compactor.block-sync-concurrency
+  [block_sync_concurrency: <int> | default = 20]
+
+  # Number of Go routines to use when syncing block meta files from the long
+  # term storage.
+  # CLI flag: -compactor.meta-sync-concurrency
+  [meta_sync_concurrency: <int> | default = 20]
+
+  # Minimum age of fresh (non-compacted) blocks before they are being processed.
+  # Malformed blocks older than the maximum of consistency-delay and 48h0m0s
+  # will be removed.
+  # CLI flag: -compactor.consistency-delay
+  [consistency_delay: <duration> | default = 30m0s]
+
+  # Data directory in which to cache blocks and process compactions
+  # CLI flag: -compactor.data-dir
+  [data_dir: <string> | default = "./data"]
+
+  # The frequency at which the compaction runs
+  # CLI flag: -compactor.compaction-interval
+  [compaction_interval: <duration> | default = 1h0m0s]
+
+  # How many times to retry a failed compaction during a single compaction
+  # interval
+  # CLI flag: -compactor.compaction-retries
+  [compaction_retries: <int> | default = 3]
+
+  # Shard tenants across multiple compactor instances. Sharding is required if
+  # you run multiple compactor instances, in order to coordinate compactions and
+  # avoid race conditions leading to the same tenant blocks simultaneously
+  # compacted by different instances.
+  # CLI flag: -compactor.sharding-enabled
+  [sharding_enabled: <boolean> | default = false]
+
+  sharding_ring:
+    kvstore:
+      # Backend storage to use for the ring. Supported values are: consul, etcd,
+      # inmemory, multi, memberlist (experimental).
+      # CLI flag: -compactor.ring.store
+      [store: <string> | default = "consul"]
+
+      # The prefix for the keys in the store. Should end with a /.
+      # CLI flag: -compactor.ring.prefix
+      [prefix: <string> | default = "collectors/"]
+
+      # The consul_config configures the consul client.
+      # The CLI flags prefix for this block config is: compactor.ring
+      [consul: <consul_config>]
+
+      # The etcd_config configures the etcd client.
+      # The CLI flags prefix for this block config is: compactor.ring
+      [etcd: <etcd_config>]
+
+      multi:
+        # Primary backend storage used by multi-client.
+        # CLI flag: -compactor.ring.multi.primary
+        [primary: <string> | default = ""]
+
+        # Secondary backend storage used by multi-client.
+        # CLI flag: -compactor.ring.multi.secondary
+        [secondary: <string> | default = ""]
+
+        # Mirror writes to secondary store.
+        # CLI flag: -compactor.ring.multi.mirror-enabled
+        [mirror_enabled: <boolean> | default = false]
+
+        # Timeout for storing value to secondary store.
+        # CLI flag: -compactor.ring.multi.mirror-timeout
+        [mirror_timeout: <duration> | default = 2s]
+
+    # Period at which to heartbeat to the ring.
+    # CLI flag: -compactor.ring.heartbeat-period
+    [heartbeat_period: <duration> | default = 5s]
+
+    # The heartbeat timeout after which compactors are considered unhealthy
+    # within the ring.
+    # CLI flag: -compactor.ring.heartbeat-timeout
+    [heartbeat_timeout: <duration> | default = 1m0s]
 ```
 
 ## Known issues
diff --git a/docs/operations/blocks-storage.template b/docs/operations/blocks-storage.template
new file mode 100644
index 0000000000..91f69df81f
--- /dev/null
+++ b/docs/operations/blocks-storage.template
@@ -0,0 +1,100 @@
+---
+title: "Blocks storage (experimental)"
+linkTitle: "Blocks storage (experimental)"
+weight: 1
+slug: blocks-storage
+---
+
+The blocks storage is an **experimental** Cortex storage engine based on [Prometheus TSDB](https://prometheus.io/docs/prometheus/latest/storage/): it stores each tenant's time series into their own TSDB which write out their series to a on-disk Block (defaults to 2h block range periods). Each Block is composed by chunk files - containing the timestamp-value pairs for multiple series - and an index, which indexes metric names and labels to time series in the chunk files.
+
+The supported backends for the blocks storage are:
+
+* [Amazon S3](https://aws.amazon.com/s3)
+* [Google Cloud Storage](https://cloud.google.com/storage/)
+* [Microsoft Azure Storage](https://azure.microsoft.com/en-us/services/storage/)
+
+_Internally, this storage engine is based on [Thanos](https://thanos.io), but no Thanos knowledge is required in order to run it._
+
+The rest of the document assumes you have read the [Cortex architecture](../architecture.md) documentation.
+
+## How it works
+
+When the blocks storage is used, each **ingester** creates a per-tenant TSDB and ships the TSDB Blocks - which by default are cut every 2 hours - to the long-term storage.
+
+**Queriers** periodically iterate over the storage bucket to discover recently uploaded Blocks and - for each Block - download a subset of the block index - called "index header" - which is kept in memory and used to provide fast lookups.
+
+### The write path
+
+**Ingesters** receive incoming samples from the distributors. Each push request belongs to a tenant, and the ingester append the received samples to the specific per-tenant TSDB. The received samples are both kept in-memory and written to a write-ahead log (WAL) stored on the local disk and used to recover the in-memory series in case the ingester abruptly terminates. The per-tenant TSDB is lazily created in each ingester upon the first push request is received for that tenant.
+
+The in-memory samples are periodically flushed to disk - and the WAL truncated - when a new TSDB Block is cut, which by default occurs every 2 hours. Each new Block cut is then uploaded to the long-term storage and kept in the ingester for some more time, in order to give queriers enough time to discover the new Block from the storage and download its index header.
+
+In order to effectively use the **WAL** and being able to recover the in-memory series upon ingester abruptly termination, the WAL needs to be stored to a persistent local disk which can survive in the event of an ingester failure (ie. AWS EBS volume or GCP persistent disk when running in the cloud). For example, if you're running the Cortex cluster in Kubernetes, you may use a StatefulSet with a persistent volume claim for the ingesters.
+
+### The read path
+
+**Queriers** - at startup - iterate over the entire storage bucket to discover all tenants Blocks and - for each of them - download the index header. During this initial synchronization phase, a querier is not ready to handle incoming queries yet and its `/ready` readiness probe endpoint will fail.
+
+Queriers also periodically re-iterate over the storage bucket to discover newly uploaded Blocks (by the ingesters) and find out Blocks deleted in the meanwhile, as effect of an optional retention policy.
+
+The blocks chunks and the entire index is never fully downloaded by the queriers. In the read path, a querier lookups the series label names and values using the in-memory index header and then download the required segments of the index and chunks for the matching series directly from the long-term storage using byte-range requests.
+
+The index header is also stored to the local disk, in order to avoid to re-download it on subsequent restarts of a querier. For this reason, it's recommended - but not required - to run the querier with a persistent local disk. For example, if you're running the Cortex cluster in Kubernetes, you may use a StatefulSet with a persistent volume claim for the queriers.
+
+### Series sharding and replication
+
+The series sharding and replication doesn't change based on the storage engine, so the general overview provided by the "[Cortex architecture](../architecture.md)" documentation applies to the blocks storage as well.
+
+It's important to note that - differently than the [chunks storage](../architecture.md#chunks-storage-default) - time series are effectively written N times to the long-term storage, where N is the replication factor (typically 3). This may lead to a storage utilization N times more than the chunks storage, but is actually mitigated by the [compactor](#compactor) service (see "vertical compaction").
+
+### Compactor
+
+The **compactor** is an optional - but highly recommended - service which compacts multiple Blocks of a given tenant into a single optimized larger Block. The compactor has two main benefits:
+
+1. Vertically compact Blocks uploaded by all ingesters for the same time range
+2. Horizontally compact Blocks with small time ranges into a single larger Block
+
+The **vertical compaction** compacts all the Blocks of a tenant uploaded by any ingester for the same Block range period (defaults to 2 hours) into a single Block, de-duplicating samples that are originally written to N Blocks as effect of the replication.
+
+The **horizontal compaction** triggers after the vertical compaction and compacts several Blocks belonging to adjacent small range periods (2 hours) into a single larger Block. Despite the total block chunks size doesn't change after this compaction, it may have a significative impact on the reduction of the index size and its index header kept in memory by queriers.
+
+The compactor is **stateless**.
+
+#### Compactor sharding
+
+The compactor optionally supports sharding. When sharding is enabled, multiple compactor instances can coordinate to split the workload and shard blocks by tenant. All the blocks of a tenant are processed by a single compactor instance at any given time, but compaction for different tenants may simultaneously run on different compactor instances.
+
+Whenever the pool of compactors increase or decrease (ie. following up a scale up/down), tenants are resharded across the available compactor instances without any manual intervention. Compactors coordinate via the Cortex [hash ring](../architecture.md#the-hash-ring).
+
+#### Compactor HTTP endpoints
+
+- `GET /compactor_ring`<br />
+  Displays the status of the compactors ring, including the tokens owned by each compactor and an option to remove (forget) instances from the ring.
+
+## Configuration
+
+The general [configuration documentation](../configuration/_index.md) also applied to a Cortex cluster running the blocks storage, with few differences:
+
+- [`storage_config`](#storage-config)
+- [`tsdb_config`](#tsdb-config)
+- [`compactor_config`](#compactor-config)
+
+### `storage_config`
+
+The `storage_config` configures the storage engine.
+
+```yaml
+storage:
+  # The storage engine to use. Use "tsdb" for the blocks storage.
+  # CLI flag: -store.engine
+  engine: tsdb
+```
+
+{{ .TSDBConfigBlock }}
+{{ .CompactorConfigBlock }}
+
+## Known issues
+
+### Migrating from the chunks to the blocks storage
+
+Currently, no smooth migration path is provided to migrate from chunks to blocks storage. For this reason, the blocks storage can only be enabled in new Cortex clusters.
diff --git a/pkg/compactor/compactor.go b/pkg/compactor/compactor.go
index 70119c40e0..278b41f7ab 100644
--- a/pkg/compactor/compactor.go
+++ b/pkg/compactor/compactor.go
@@ -54,7 +54,7 @@ func (cfg *Config) RegisterFlags(f *flag.FlagSet) {
 	cfg.retryMinBackoff = 10 * time.Second
 	cfg.retryMaxBackoff = time.Minute
 
-	f.Var(&cfg.BlockRanges, "compactor.block-ranges", "Comma separated list of compaction ranges expressed in the time duration format")
+	f.Var(&cfg.BlockRanges, "compactor.block-ranges", "List of compaction time ranges.")
 	f.DurationVar(&cfg.ConsistencyDelay, "compactor.consistency-delay", 30*time.Minute, fmt.Sprintf("Minimum age of fresh (non-compacted) blocks before they are being processed. Malformed blocks older than the maximum of consistency-delay and %s will be removed.", compact.PartialUploadThresholdAge))
 	f.IntVar(&cfg.BlockSyncConcurrency, "compactor.block-sync-concurrency", 20, "Number of Go routines to use when syncing block index and chunks files from the long term storage.")
 	f.IntVar(&cfg.MetaSyncConcurrency, "compactor.meta-sync-concurrency", 20, "Number of Go routines to use when syncing block meta files from the long term storage.")
@@ -305,8 +305,10 @@ func (c *Compactor) compactUser(ctx context.Context, userID string) error {
 	reg := prometheus.NewRegistry()
 	defer c.syncerMetrics.gatherThanosSyncerMetrics(reg)
 
+	ulogger := util.WithUserID(userID, c.logger)
+
 	fetcher, err := block.NewMetaFetcher(
-		c.logger,
+		ulogger,
 		c.compactorCfg.MetaSyncConcurrency,
 		bucket,
 		// The fetcher stores cached metas in the "meta-syncer/" sub directory,
@@ -321,7 +323,7 @@ func (c *Compactor) compactUser(ctx context.Context, userID string) error {
 	}
 
 	syncer, err := compact.NewSyncer(
-		c.logger,
+		ulogger,
 		reg,
 		bucket,
 		fetcher,
@@ -334,7 +336,7 @@ func (c *Compactor) compactUser(ctx context.Context, userID string) error {
 	}
 
 	compactor, err := compact.NewBucketCompactor(
-		c.logger,
+		ulogger,
 		syncer,
 		c.tsdbCompactor,
 		path.Join(c.compactorCfg.DataDir, "compact"),
diff --git a/pkg/cortex/cortex.go b/pkg/cortex/cortex.go
index b8cc7d2293..5873090788 100644
--- a/pkg/cortex/cortex.go
+++ b/pkg/cortex/cortex.go
@@ -77,8 +77,8 @@ type Config struct {
 	QueryRange     queryrange.Config        `yaml:"query_range,omitempty"`
 	TableManager   chunk.TableManagerConfig `yaml:"table_manager,omitempty"`
 	Encoding       encoding.Config          `yaml:"-"` // No yaml for this, it only works with flags.
-	TSDB           tsdb.Config              `yaml:"tsdb" doc:"hidden"`
-	Compactor      compactor.Config         `yaml:"compactor,omitempty" doc:"hidden"`
+	TSDB           tsdb.Config              `yaml:"tsdb"`
+	Compactor      compactor.Config         `yaml:"compactor,omitempty"`
 
 	Ruler         ruler.Config                               `yaml:"ruler,omitempty"`
 	ConfigDB      db.Config                                  `yaml:"configdb,omitempty"`
diff --git a/pkg/ingester/ingester_v2.go b/pkg/ingester/ingester_v2.go
index 62b6866e79..9895e355d1 100644
--- a/pkg/ingester/ingester_v2.go
+++ b/pkg/ingester/ingester_v2.go
@@ -599,11 +599,11 @@ func (i *Ingester) getOrCreateTSDB(userID string, force bool) (*userTSDB, error)
 // createTSDB creates a TSDB for a given userID, and returns the created db.
 func (i *Ingester) createTSDB(userID string) (*userTSDB, error) {
 	tsdbPromReg := prometheus.NewRegistry()
-
 	udir := i.cfg.TSDBConfig.BlocksDir(userID)
+	userLogger := util.WithUserID(userID, util.Logger)
 
 	// Create a new user database
-	db, err := tsdb.Open(udir, util.Logger, tsdbPromReg, &tsdb.Options{
+	db, err := tsdb.Open(udir, userLogger, tsdbPromReg, &tsdb.Options{
 		RetentionDuration: uint64(i.cfg.TSDBConfig.Retention / time.Millisecond),
 		BlockRanges:       i.cfg.TSDBConfig.BlockRanges.ToMilliseconds(),
 		NoLockfile:        true,
@@ -633,7 +633,7 @@ func (i *Ingester) createTSDB(userID string) (*userTSDB, error) {
 	// Create a new shipper for this database
 	if i.cfg.TSDBConfig.ShipInterval > 0 {
 		userDB.shipper = shipper.New(
-			util.Logger,
+			userLogger,
 			tsdbPromReg,
 			udir,
 			cortex_tsdb.NewUserBucketClient(userID, i.TSDBState.bucket),
diff --git a/pkg/querier/block_store.go b/pkg/querier/block_store.go
index fb1dc716c8..da299766d1 100644
--- a/pkg/querier/block_store.go
+++ b/pkg/querier/block_store.go
@@ -25,6 +25,7 @@ import (
 	"google.golang.org/grpc/metadata"
 
 	"github.com/cortexproject/cortex/pkg/storage/tsdb"
+	"github.com/cortexproject/cortex/pkg/util"
 	"github.com/cortexproject/cortex/pkg/util/spanlogger"
 )
 
@@ -349,14 +350,16 @@ func (u *UserStore) getOrCreateStore(userID string) (*store.BucketStore, error)
 		return bs, nil
 	}
 
-	level.Info(u.logger).Log("msg", "creating user bucket store", "user", userID)
+	userLogger := util.WithUserID(userID, u.logger)
+
+	level.Info(userLogger).Log("msg", "creating user bucket store")
 
 	userBkt := tsdb.NewUserBucketClient(userID, u.bucket)
 
 	reg := prometheus.NewRegistry()
 	indexCacheSizeBytes := u.cfg.BucketStore.IndexCacheSizeBytes
 	maxItemSizeBytes := indexCacheSizeBytes / 2
-	indexCache, err := storecache.NewInMemoryIndexCacheWithConfig(u.logger, reg, storecache.InMemoryIndexCacheConfig{
+	indexCache, err := storecache.NewInMemoryIndexCacheWithConfig(userLogger, reg, storecache.InMemoryIndexCacheConfig{
 		MaxSize:     storecache.Bytes(indexCacheSizeBytes),
 		MaxItemSize: storecache.Bytes(maxItemSizeBytes),
 	})
@@ -365,7 +368,7 @@ func (u *UserStore) getOrCreateStore(userID string) (*store.BucketStore, error)
 	}
 
 	fetcher, err := block.NewMetaFetcher(
-		u.logger,
+		userLogger,
 		u.cfg.BucketStore.MetaSyncConcurrency,
 		userBkt,
 		filepath.Join(u.cfg.BucketStore.SyncDir, userID), // The fetcher stores cached metas in the "meta-syncer/" sub directory
@@ -377,7 +380,7 @@ func (u *UserStore) getOrCreateStore(userID string) (*store.BucketStore, error)
 	}
 
 	bs, err = store.NewBucketStore(
-		u.logger,
+		userLogger,
 		reg,
 		userBkt,
 		fetcher,
diff --git a/pkg/storage/tsdb/backend/s3/config.go b/pkg/storage/tsdb/backend/s3/config.go
index 04921062aa..60e52d9f9b 100644
--- a/pkg/storage/tsdb/backend/s3/config.go
+++ b/pkg/storage/tsdb/backend/s3/config.go
@@ -21,5 +21,5 @@ func (cfg *Config) RegisterFlags(f *flag.FlagSet) {
 	f.Var(&cfg.SecretAccessKey, "experimental.tsdb.s3.secret-access-key", "S3 secret access key")
 	f.StringVar(&cfg.BucketName, "experimental.tsdb.s3.bucket-name", "", "S3 bucket name")
 	f.StringVar(&cfg.Endpoint, "experimental.tsdb.s3.endpoint", "", "S3 endpoint without schema")
-	f.BoolVar(&cfg.Insecure, "experimental.tsdb.s3.insecure", false, "If enabled, use http:// for the S3 endpoint instead of https://")
+	f.BoolVar(&cfg.Insecure, "experimental.tsdb.s3.insecure", false, "If enabled, use http:// for the S3 endpoint instead of https://. This could be useful in local dev/test environments while using an S3-compatible backend storage, like Minio.")
 }
diff --git a/pkg/storage/tsdb/config.go b/pkg/storage/tsdb/config.go
index 0c84200d21..b40d097b7b 100644
--- a/pkg/storage/tsdb/config.go
+++ b/pkg/storage/tsdb/config.go
@@ -105,12 +105,12 @@ func (cfg *Config) RegisterFlags(f *flag.FlagSet) {
 		cfg.BlockRanges = []time.Duration{2 * time.Hour} // Default 2h block
 	}
 
-	f.StringVar(&cfg.Dir, "experimental.tsdb.dir", "tsdb", "directory to place all TSDB's into")
-	f.Var(&cfg.BlockRanges, "experimental.tsdb.block-ranges-period", "comma separated list of TSDB block ranges in time.Duration format")
-	f.DurationVar(&cfg.Retention, "experimental.tsdb.retention-period", 6*time.Hour, "TSDB block retention")
+	f.StringVar(&cfg.Dir, "experimental.tsdb.dir", "tsdb", "Local directory to store TSDBs in the ingesters.")
+	f.Var(&cfg.BlockRanges, "experimental.tsdb.block-ranges-period", "TSDB blocks range period.")
+	f.DurationVar(&cfg.Retention, "experimental.tsdb.retention-period", 6*time.Hour, "TSDB blocks retention in the ingester before a block is removed. This should be larger than the block_ranges_period and large enough to give ingesters enough time to discover newly uploaded blocks.")
 	f.DurationVar(&cfg.ShipInterval, "experimental.tsdb.ship-interval", 1*time.Minute, "How frequently the TSDB blocks are scanned and new ones are shipped to the storage. 0 means shipping is disabled.")
 	f.IntVar(&cfg.ShipConcurrency, "experimental.tsdb.ship-concurrency", 10, "Maximum number of tenants concurrently shipping blocks to the storage.")
-	f.StringVar(&cfg.Backend, "experimental.tsdb.backend", "s3", "TSDB storage backend to use")
+	f.StringVar(&cfg.Backend, "experimental.tsdb.backend", "s3", `Backend storage to use. Either "s3" or "gcs".`)
 	f.IntVar(&cfg.MaxTSDBOpeningConcurrencyOnStartup, "experimental.tsdb.max-tsdb-opening-concurrency-on-startup", 10, "limit the number of concurrently opening TSDB's on startup")
 	f.DurationVar(&cfg.HeadCompactionInterval, "experimental.tsdb.head-compaction-interval", 1*time.Minute, "How frequently does Cortex try to compact TSDB head. Block is only created if data covers smallest block range. Must be greater than 0 and max 5 minutes.")
 	f.IntVar(&cfg.HeadCompactionConcurrency, "experimental.tsdb.head-compaction-concurrency", 5, "Maximum number of tenants concurrently compacting TSDB head into a new block")
@@ -152,12 +152,12 @@ type BucketStoreConfig struct {
 
 // RegisterFlags registers the BucketStore flags
 func (cfg *BucketStoreConfig) RegisterFlags(f *flag.FlagSet) {
-	f.StringVar(&cfg.SyncDir, "experimental.tsdb.bucket-store.sync-dir", "tsdb-sync", "Directory to place synced tsdb indicies.")
+	f.StringVar(&cfg.SyncDir, "experimental.tsdb.bucket-store.sync-dir", "tsdb-sync", "Directory to store synchronized TSDB index headers.")
 	f.DurationVar(&cfg.SyncInterval, "experimental.tsdb.bucket-store.sync-interval", 5*time.Minute, "How frequently scan the bucket to look for changes (new blocks shipped by ingesters and blocks removed by retention or compaction). 0 disables it.")
-	f.Uint64Var(&cfg.IndexCacheSizeBytes, "experimental.tsdb.bucket-store.index-cache-size-bytes", uint64(250*units.Mebibyte), "Size of index cache in bytes per tenant.")
-	f.Uint64Var(&cfg.MaxChunkPoolBytes, "experimental.tsdb.bucket-store.max-chunk-pool-bytes", uint64(2*units.Gibibyte), "Max size of chunk pool in bytes per tenant.")
-	f.Uint64Var(&cfg.MaxSampleCount, "experimental.tsdb.bucket-store.max-sample-count", 0, "Max number of samples (0 is no limit) per query when loading series from storage.")
-	f.IntVar(&cfg.MaxConcurrent, "experimental.tsdb.bucket-store.max-concurrent", 20, "Max number of concurrent queries to the storage per tenant.")
+	f.Uint64Var(&cfg.IndexCacheSizeBytes, "experimental.tsdb.bucket-store.index-cache-size-bytes", uint64(250*units.Mebibyte), "Size - in bytes - of a per-tenant in-memory index cache used to speed up blocks index lookups.")
+	f.Uint64Var(&cfg.MaxChunkPoolBytes, "experimental.tsdb.bucket-store.max-chunk-pool-bytes", uint64(2*units.Gibibyte), "Max size - in bytes - of a per-tenant chunk pool, used to reduce memory allocations.")
+	f.Uint64Var(&cfg.MaxSampleCount, "experimental.tsdb.bucket-store.max-sample-count", 0, "Max number of samples per query when loading series from the long-term storage. 0 disables the limit.")
+	f.IntVar(&cfg.MaxConcurrent, "experimental.tsdb.bucket-store.max-concurrent", 20, "Max number of concurrent queries to execute against the long-term storage on a per-tenant basis.")
 	f.IntVar(&cfg.TenantSyncConcurrency, "experimental.tsdb.bucket-store.tenant-sync-concurrency", 10, "Maximum number of concurrent tenants synching blocks.")
 	f.IntVar(&cfg.BlockSyncConcurrency, "experimental.tsdb.bucket-store.block-sync-concurrency", 20, "Maximum number of concurrent blocks synching per tenant.")
 	f.IntVar(&cfg.MetaSyncConcurrency, "experimental.tsdb.bucket-store.meta-sync-concurrency", 20, "Number of Go routines to use when syncing block meta files from object storage per tenant.")
diff --git a/tools/doc-generator/main.go b/tools/doc-generator/main.go
index c7e7601222..190ca8353c 100644
--- a/tools/doc-generator/main.go
+++ b/tools/doc-generator/main.go
@@ -1,10 +1,13 @@
 package main
 
 import (
+	"flag"
 	"fmt"
 	"os"
+	"path/filepath"
 	"reflect"
 	"strings"
+	"text/template"
 
 	"github.com/weaveworks/common/server"
 
@@ -12,6 +15,7 @@ import (
 	"github.com/cortexproject/cortex/pkg/chunk"
 	"github.com/cortexproject/cortex/pkg/chunk/cache"
 	"github.com/cortexproject/cortex/pkg/chunk/storage"
+	"github.com/cortexproject/cortex/pkg/compactor"
 	config_client "github.com/cortexproject/cortex/pkg/configs/client"
 	"github.com/cortexproject/cortex/pkg/configs/db"
 	"github.com/cortexproject/cortex/pkg/cortex"
@@ -25,6 +29,7 @@ import (
 	"github.com/cortexproject/cortex/pkg/ring/kv/etcd"
 	"github.com/cortexproject/cortex/pkg/ring/kv/memberlist"
 	"github.com/cortexproject/cortex/pkg/ruler"
+	"github.com/cortexproject/cortex/pkg/storage/tsdb"
 	"github.com/cortexproject/cortex/pkg/util/validation"
 )
 
@@ -152,6 +157,16 @@ var (
 			structType: reflect.TypeOf(config_client.Config{}),
 			desc:       "The configstore_config configures the config database storing rules and alerts, and is used by the Cortex alertmanager.",
 		},
+		{
+			name:       "tsdb_config",
+			structType: reflect.TypeOf(tsdb.Config{}),
+			desc:       "The tsdb_config configures the experimental blocks storage.",
+		},
+		{
+			name:       "compactor_config",
+			structType: reflect.TypeOf(compactor.Config{}),
+			desc:       "The compactor_config configures the compactor for the experimental blocks storage.",
+		},
 	}
 )
 
@@ -214,12 +229,59 @@ func annotateFlagPrefix(blocks []*configBlock) {
 	}
 }
 
+func generateBlocksMarkdown(blocks []*configBlock) string {
+	md := &markdownWriter{}
+	md.writeConfigDoc(blocks)
+	return md.string()
+}
+
+func generateBlockMarkdown(blocks []*configBlock, blockName, fieldName string) string {
+	// Look for the requested block.
+	for _, block := range blocks {
+		if block.name != blockName {
+			continue
+		}
+
+		md := &markdownWriter{}
+
+		// Wrap the root block with another block, so that we can show the name of the
+		// root field containing the block specs.
+		md.writeConfigBlock(&configBlock{
+			name: blockName,
+			desc: block.desc,
+			entries: []*configEntry{
+				{
+					kind:      "block",
+					name:      fieldName,
+					required:  true,
+					block:     block,
+					blockDesc: "",
+					root:      false,
+				},
+			},
+		})
+
+		return md.string()
+	}
+
+	// If the block has not been found, we return an empty string.
+	return ""
+}
+
 func main() {
-	cfg := &cortex.Config{}
+	// Parse the generator flags.
+	flag.Parse()
+	if flag.NArg() != 1 {
+		fmt.Fprintf(os.Stderr, "Usage: doc-generator template-file")
+		os.Exit(1)
+	}
+
+	templatePath := flag.Arg(0)
 
 	// In order to match YAML config fields with CLI flags, we do map
 	// the memory address of the CLI flag variables and match them with
 	// the config struct fields address.
+	cfg := &cortex.Config{}
 	flags := parseFlags(cfg)
 
 	// Parse the config, mapping each config field with the related CLI flag.
@@ -233,8 +295,28 @@ func main() {
 	// prefix wherever encountered in the config blocks.
 	annotateFlagPrefix(blocks)
 
-	// Generate markdown
-	md := &markdownWriter{}
-	md.writeConfigDoc(blocks)
-	fmt.Println(md.string())
+	// Generate documentation markdown.
+	data := struct {
+		ConfigFile           string
+		TSDBConfigBlock      string
+		CompactorConfigBlock string
+	}{
+		ConfigFile:           generateBlocksMarkdown(blocks),
+		TSDBConfigBlock:      generateBlockMarkdown(blocks, "tsdb_config", "tsdb"),
+		CompactorConfigBlock: generateBlockMarkdown(blocks, "compactor_config", "compactor"),
+	}
+
+	// Load the template file.
+	tpl := template.New(filepath.Base(templatePath))
+	tpl, err = tpl.ParseFiles(templatePath)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "An error occurred while loading the template %s: %s\n", templatePath, err.Error())
+		os.Exit(1)
+	}
+
+	// Execute the template to inject generated doc.
+	if err := tpl.Execute(os.Stdout, data); err != nil {
+		fmt.Fprintf(os.Stderr, "An error occurred while executing the template %s: %s\n", templatePath, err.Error())
+		os.Exit(1)
+	}
 }
diff --git a/tools/doc-generator/writer.go b/tools/doc-generator/writer.go
index 73a1bd0ba9..53099728b7 100644
--- a/tools/doc-generator/writer.go
+++ b/tools/doc-generator/writer.go
@@ -123,7 +123,7 @@ func (w *markdownWriter) writeConfigDoc(blocks []*configBlock) {
 func (w *markdownWriter) writeConfigBlock(block *configBlock) {
 	// Title
 	if block.name != "" {
-		w.out.WriteString("## `" + block.name + "`\n")
+		w.out.WriteString("### `" + block.name + "`\n")
 		w.out.WriteString("\n")
 	}
 


Test patch:
diff --git a/pkg/compactor/compactor_test.go b/pkg/compactor/compactor_test.go
index f03e2bae69..5c8ac29e89 100644
--- a/pkg/compactor/compactor_test.go
+++ b/pkg/compactor/compactor_test.go
@@ -294,16 +294,16 @@ func TestCompactor_ShouldIterateOverUsersAndRunCompaction(t *testing.T) {
 		`level=info msg="discovering users from bucket"`,
 		`level=info msg="discovered users from bucket" users=2`,
 		`level=info msg="starting compaction of user blocks" user=user-1`,
-		`level=info msg="start sync of metas"`,
-		`level=info msg="start of GC"`,
-		`level=info msg="start of compaction"`,
-		`level=info msg="compaction iterations done"`,
+		`level=info org_id=user-1 msg="start sync of metas"`,
+		`level=info org_id=user-1 msg="start of GC"`,
+		`level=info org_id=user-1 msg="start of compaction"`,
+		`level=info org_id=user-1 msg="compaction iterations done"`,
 		`level=info msg="successfully compacted user blocks" user=user-1`,
 		`level=info msg="starting compaction of user blocks" user=user-2`,
-		`level=info msg="start sync of metas"`,
-		`level=info msg="start of GC"`,
-		`level=info msg="start of compaction"`,
-		`level=info msg="compaction iterations done"`,
+		`level=info org_id=user-2 msg="start sync of metas"`,
+		`level=info org_id=user-2 msg="start of GC"`,
+		`level=info org_id=user-2 msg="start of compaction"`,
+		`level=info org_id=user-2 msg="compaction iterations done"`,
 		`level=info msg="successfully compacted user blocks" user=user-2`,
 	}, removeMetaFetcherLogs(strings.Split(strings.TrimSpace(logs.String()), "\n")))
 
@@ -368,16 +368,16 @@ func TestCompactor_ShouldCompactAllUsersOnShardingEnabledButOnlyOneInstanceRunni
 		`level=info msg="discovering users from bucket"`,
 		`level=info msg="discovered users from bucket" users=2`,
 		`level=info msg="starting compaction of user blocks" user=user-1`,
-		`level=info msg="start sync of metas"`,
-		`level=info msg="start of GC"`,
-		`level=info msg="start of compaction"`,
-		`level=info msg="compaction iterations done"`,
+		`level=info org_id=user-1 msg="start sync of metas"`,
+		`level=info org_id=user-1 msg="start of GC"`,
+		`level=info org_id=user-1 msg="start of compaction"`,
+		`level=info org_id=user-1 msg="compaction iterations done"`,
 		`level=info msg="successfully compacted user blocks" user=user-1`,
 		`level=info msg="starting compaction of user blocks" user=user-2`,
-		`level=info msg="start sync of metas"`,
-		`level=info msg="start of GC"`,
-		`level=info msg="start of compaction"`,
-		`level=info msg="compaction iterations done"`,
+		`level=info org_id=user-2 msg="start sync of metas"`,
+		`level=info org_id=user-2 msg="start of GC"`,
+		`level=info org_id=user-2 msg="start of compaction"`,
+		`level=info org_id=user-2 msg="compaction iterations done"`,
 		`level=info msg="successfully compacted user blocks" user=user-2`,
 	}, removeMetaFetcherLogs(strings.Split(strings.TrimSpace(logs.String()), "\n")))
 }