Return-Path: Delivered-To: apmail-cassandra-commits-archive@www.apache.org Received: (qmail 20945 invoked from network); 18 Sep 2010 22:08:31 -0000 Received: from unknown (HELO mail.apache.org) (140.211.11.3) by 140.211.11.9 with SMTP; 18 Sep 2010 22:08:31 -0000 Received: (qmail 63391 invoked by uid 500); 18 Sep 2010 22:08:31 -0000 Delivered-To: apmail-cassandra-commits-archive@cassandra.apache.org Received: (qmail 63359 invoked by uid 500); 18 Sep 2010 22:08:30 -0000 Mailing-List: contact commits-help@cassandra.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@cassandra.apache.org Delivered-To: mailing list commits@cassandra.apache.org Received: (qmail 63351 invoked by uid 99); 18 Sep 2010 22:08:30 -0000 Received: from athena.apache.org (HELO athena.apache.org) (140.211.11.136) by apache.org (qpsmtpd/0.29) with ESMTP; Sat, 18 Sep 2010 22:08:30 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=10.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Sat, 18 Sep 2010 22:08:28 +0000 Received: by eris.apache.org (Postfix, from userid 65534) id 10A2323889B2; Sat, 18 Sep 2010 22:08:08 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r998558 - in /cassandra/trunk: ./ conf/ interface/ interface/thrift/gen-java/org/apache/cassandra/thrift/ src/avro/ src/java/org/apache/cassandra/avro/ src/java/org/apache/cassandra/config/ src/java/org/apache/cassandra/db/ src/java/org/apa... Date: Sat, 18 Sep 2010 22:08:07 -0000 To: commits@cassandra.apache.org From: jbellis@apache.org X-Mailer: svnmailer-1.0.8 Message-Id: <20100918220808.10A2323889B2@eris.apache.org> Author: jbellis Date: Sat Sep 18 22:08:06 2010 New Revision: 998558 URL: http://svn.apache.org/viewvc?rev=998558&view=rev Log: min,max compaction threshold are configurable and persistent per-ColumnFamily. patch by Jon Hermes; reviewed by jbellis for CASSANDRA-1468 Modified: cassandra/trunk/CHANGES.txt cassandra/trunk/conf/cassandra.yaml cassandra/trunk/interface/cassandra.genavro cassandra/trunk/interface/cassandra.thrift cassandra/trunk/interface/thrift/gen-java/org/apache/cassandra/thrift/CfDef.java cassandra/trunk/src/avro/internode.genavro cassandra/trunk/src/java/org/apache/cassandra/avro/CassandraServer.java cassandra/trunk/src/java/org/apache/cassandra/config/CFMetaData.java cassandra/trunk/src/java/org/apache/cassandra/config/DatabaseDescriptor.java cassandra/trunk/src/java/org/apache/cassandra/config/RawColumnFamily.java cassandra/trunk/src/java/org/apache/cassandra/db/ColumnFamilyStore.java cassandra/trunk/src/java/org/apache/cassandra/db/ColumnFamilyStoreMBean.java cassandra/trunk/src/java/org/apache/cassandra/db/CompactionManager.java cassandra/trunk/src/java/org/apache/cassandra/db/CompactionManagerMBean.java cassandra/trunk/src/java/org/apache/cassandra/thrift/CassandraServer.java cassandra/trunk/src/java/org/apache/cassandra/tools/NodeCmd.java cassandra/trunk/src/java/org/apache/cassandra/tools/NodeProbe.java cassandra/trunk/test/unit/org/apache/cassandra/db/DefsTest.java Modified: cassandra/trunk/CHANGES.txt URL: http://svn.apache.org/viewvc/cassandra/trunk/CHANGES.txt?rev=998558&r1=998557&r2=998558&view=diff ============================================================================== --- cassandra/trunk/CHANGES.txt (original) +++ cassandra/trunk/CHANGES.txt Sat Sep 18 22:08:06 2010 @@ -77,6 +77,8 @@ * clean up of Streaming system (CASSANDRA-1503, 1504, 1506) * add options to configure Thrift socket keepalive and buffer sizes (CASSANDRA-1426) * make contrib CassandraServiceDataCleaner recursive (CASSANDRA-1509) + * min, max compaction threshold are configurable and persistent + per-ColumnFamily (CASSANDRA-1468) 0.7-beta1 Modified: cassandra/trunk/conf/cassandra.yaml URL: http://svn.apache.org/viewvc/cassandra/trunk/conf/cassandra.yaml?rev=998558&r1=998557&r2=998558&view=diff ============================================================================== --- cassandra/trunk/conf/cassandra.yaml (original) +++ cassandra/trunk/conf/cassandra.yaml Sat Sep 18 22:08:06 2010 @@ -315,6 +315,8 @@ keyspaces: read_repair_chance: 0.1 keys_cached: 100 gc_grace_seconds: 0 + min_compaction_threshold: 5 + max_compaction_threshold: 31 - name: StandardByUUID1 compare_with: TimeUUIDType Modified: cassandra/trunk/interface/cassandra.genavro URL: http://svn.apache.org/viewvc/cassandra/trunk/interface/cassandra.genavro?rev=998558&r1=998557&r2=998558&view=diff ============================================================================== --- cassandra/trunk/interface/cassandra.genavro (original) +++ cassandra/trunk/interface/cassandra.genavro Sat Sep 18 22:08:06 2010 @@ -155,6 +155,8 @@ protocol Cassandra { union { double, null } read_repair_chance; union { int, null } gc_grace_seconds; union { null, string } default_validation_class = null; + union { null, int } min_compaction_threshold = null; + union { null, int } max_compaction_threshold = null; union { int, null } id; union { array, null } column_metadata; } Modified: cassandra/trunk/interface/cassandra.thrift URL: http://svn.apache.org/viewvc/cassandra/trunk/interface/cassandra.thrift?rev=998558&r1=998557&r2=998558&view=diff ============================================================================== --- cassandra/trunk/interface/cassandra.thrift (original) +++ cassandra/trunk/interface/cassandra.thrift Sat Sep 18 22:08:06 2010 @@ -46,7 +46,7 @@ namespace rb CassandraThrift # for every edit that doesn't result in a change to major/minor. # # See the Semantic Versioning Specification (SemVer) http://semver.org. -const string VERSION = "16.0.0" +const string VERSION = "16.1.0" # @@ -355,6 +355,8 @@ struct CfDef { 14: optional i32 gc_grace_seconds, 15: optional string default_validation_class, 16: optional i32 id, + 17: optional i32 min_compaction_threshold, + 18: optional i32 max_compaction_threshold, } /* describes a keyspace. */ Modified: cassandra/trunk/interface/thrift/gen-java/org/apache/cassandra/thrift/CfDef.java URL: http://svn.apache.org/viewvc/cassandra/trunk/interface/thrift/gen-java/org/apache/cassandra/thrift/CfDef.java?rev=998558&r1=998557&r2=998558&view=diff ============================================================================== --- cassandra/trunk/interface/thrift/gen-java/org/apache/cassandra/thrift/CfDef.java (original) +++ cassandra/trunk/interface/thrift/gen-java/org/apache/cassandra/thrift/CfDef.java Sat Sep 18 22:08:06 2010 @@ -65,6 +65,8 @@ public class CfDef implements TBase byName = new HashMap(); @@ -147,6 +153,10 @@ public class CfDef implements TBase metaDataMap; static { @@ -231,6 +243,10 @@ public class CfDef implements TBase, null } column_metadata; } Modified: cassandra/trunk/src/java/org/apache/cassandra/avro/CassandraServer.java URL: http://svn.apache.org/viewvc/cassandra/trunk/src/java/org/apache/cassandra/avro/CassandraServer.java?rev=998558&r1=998557&r2=998558&view=diff ============================================================================== --- cassandra/trunk/src/java/org/apache/cassandra/avro/CassandraServer.java (original) +++ cassandra/trunk/src/java/org/apache/cassandra/avro/CassandraServer.java Sat Sep 18 22:08:06 2010 @@ -866,6 +866,8 @@ public class CassandraServer implements cf_def.read_repair_chance == null ? CFMetaData.DEFAULT_READ_REPAIR_CHANCE : cf_def.read_repair_chance, cf_def.gc_grace_seconds != null ? cf_def.gc_grace_seconds : CFMetaData.DEFAULT_GC_GRACE_SECONDS, DatabaseDescriptor.getComparator(validate), + cf_def.min_compaction_threshold == null ? CFMetaData.DEFAULT_MIN_COMPACTION_THRESHOLD : cf_def.min_compaction_threshold, + cf_def.max_compaction_threshold == null ? CFMetaData.DEFAULT_MAX_COMPACTION_THRESHOLD : cf_def.max_compaction_threshold, ColumnDefinition.fromColumnDefs((Iterable) cf_def.column_metadata)); } Modified: cassandra/trunk/src/java/org/apache/cassandra/config/CFMetaData.java URL: http://svn.apache.org/viewvc/cassandra/trunk/src/java/org/apache/cassandra/config/CFMetaData.java?rev=998558&r1=998557&r2=998558&view=diff ============================================================================== --- cassandra/trunk/src/java/org/apache/cassandra/config/CFMetaData.java (original) +++ cassandra/trunk/src/java/org/apache/cassandra/config/CFMetaData.java Sat Sep 18 22:08:06 2010 @@ -49,11 +49,14 @@ import org.apache.cassandra.utils.Pair; public final class CFMetaData { - public final static double DEFAULT_READ_REPAIR_CHANCE = 1.0; - public final static double DEFAULT_KEY_CACHE_SIZE = 200000; public final static double DEFAULT_ROW_CACHE_SIZE = 0.0; + public final static double DEFAULT_KEY_CACHE_SIZE = 200000; + public final static double DEFAULT_READ_REPAIR_CHANCE = 1.0; public final static boolean DEFAULT_PRELOAD_ROW_CACHE = false; public final static int DEFAULT_GC_GRACE_SECONDS = 864000; + public final static int DEFAULT_MIN_COMPACTION_THRESHOLD = 4; + public final static int DEFAULT_MAX_COMPACTION_THRESHOLD = 32; + private static final int MIN_CF_ID = 1000; private static final AtomicInteger idGen = new AtomicInteger(MIN_CF_ID); @@ -81,6 +84,8 @@ public final class CFMetaData 0, 0, BytesType.instance, + DEFAULT_MIN_COMPACTION_THRESHOLD, + DEFAULT_MAX_COMPACTION_THRESHOLD, cfId, Collections.emptyMap()); } @@ -117,20 +122,23 @@ public final class CFMetaData } public final Integer cfId; - public final String tableName; // name of table which has this column family - public final String cfName; // name of the column family - public final ColumnFamilyType cfType; // type: super, standard, etc. - public final ClockType clockType; // clock type: timestamp, etc. - public final AbstractType comparator; // name sorted, time stamp sorted etc. - public final AbstractType subcolumnComparator; // like comparator, for supercolumns - public final AbstractReconciler reconciler; // determine correct column from conflicting versions - public final String comment; // for humans only - public final double rowCacheSize; // default 0 - public final double keyCacheSize; // default 0.01 - public final double readRepairChance; //chance 0 to 1, of doing a read repair; defaults 1.0 (always) - public final boolean preloadRowCache; - public final int gcGraceSeconds; // default 864000 (ten days) - public final AbstractType defaultValidator; // values are longs, strings, bytes (no-op)... + public final String tableName; // name of table which has this column family + public final String cfName; // name of the column family + public final ColumnFamilyType cfType; // type: super, standard, etc. + public final ClockType clockType; // clock type: timestamp, etc. + public final AbstractType comparator; // name sorted, time stamp sorted etc. + public final AbstractType subcolumnComparator; // like comparator, for supercolumns + public final AbstractReconciler reconciler; // determine correct column from conflicting versions + public final String comment; // for humans only + + public final double rowCacheSize; // default 0 + public final double keyCacheSize; // default 0.01 + public final double readRepairChance; // default 1.0 (always), chance [0.0,1.0] of read repair + public final boolean preloadRowCache; // default false + public final int gcGraceSeconds; // default 864000 (ten days) + public final AbstractType defaultValidator; // default none, use comparator types + public final Integer minCompactionThreshold; // default 4 + public final Integer maxCompactionThreshold; // default 32 // NOTE: if you find yourself adding members to this class, make sure you keep the convert methods in lockstep. public final Map column_metadata; @@ -149,6 +157,8 @@ public final class CFMetaData double readRepairChance, int gcGraceSeconds, AbstractType defaultValidator, + int minCompactionThreshold, + int maxCompactionThreshold, Integer cfId, Map column_metadata) { @@ -169,6 +179,8 @@ public final class CFMetaData this.readRepairChance = readRepairChance; this.gcGraceSeconds = gcGraceSeconds; this.defaultValidator = defaultValidator; + this.minCompactionThreshold = minCompactionThreshold; + this.maxCompactionThreshold = maxCompactionThreshold; this.cfId = cfId; this.column_metadata = Collections.unmodifiableMap(column_metadata); } @@ -185,21 +197,89 @@ public final class CFMetaData } } - public CFMetaData(String tableName, String cfName, ColumnFamilyType cfType, ClockType clockType, AbstractType comparator, AbstractType subcolumnComparator, AbstractReconciler reconciler, String comment, double rowCacheSize, boolean preloadRowCache, double keyCacheSize, double readRepairChance, int gcGraceSeconds, AbstractType defaultvalidator, Map column_metadata) - { - this(tableName, cfName, cfType, clockType, comparator, subcolumnComparator, reconciler, comment, rowCacheSize, preloadRowCache, keyCacheSize, readRepairChance, gcGraceSeconds, defaultvalidator, nextId(), column_metadata); + public CFMetaData(String tableName, + String cfName, + ColumnFamilyType cfType, + ClockType clockType, + AbstractType comparator, + AbstractType subcolumnComparator, + AbstractReconciler reconciler, + String comment, + double rowCacheSize, + boolean preloadRowCache, + double keyCacheSize, + double readRepairChance, + int gcGraceSeconds, + AbstractType defaultValidator, + int minCompactionThreshold, + int maxCompactionThreshold, + //This constructor generates the id! + Map column_metadata) + { + this(tableName, + cfName, + cfType, + clockType, + comparator, + subcolumnComparator, + reconciler, + comment, + rowCacheSize, + preloadRowCache, + keyCacheSize, + readRepairChance, + gcGraceSeconds, + defaultValidator, + minCompactionThreshold, + maxCompactionThreshold, + nextId(), + column_metadata); } /** clones an existing CFMetaData using the same id. */ public static CFMetaData rename(CFMetaData cfm, String newName) { - return new CFMetaData(cfm.tableName, newName, cfm.cfType, cfm.clockType, cfm.comparator, cfm.subcolumnComparator, cfm.reconciler, cfm.comment, cfm.rowCacheSize, cfm.preloadRowCache, cfm.keyCacheSize, cfm.readRepairChance, cfm.gcGraceSeconds, cfm.defaultValidator, cfm.cfId, cfm.column_metadata); + return new CFMetaData(cfm.tableName, + newName, + cfm.cfType, + cfm.clockType, + cfm.comparator, + cfm.subcolumnComparator, + cfm.reconciler, + cfm.comment, + cfm.rowCacheSize, + cfm.preloadRowCache, + cfm.keyCacheSize, + cfm.readRepairChance, + cfm.gcGraceSeconds, + cfm.defaultValidator, + cfm.minCompactionThreshold, + cfm.maxCompactionThreshold, + cfm.cfId, + cfm.column_metadata); } /** clones existing CFMetaData. keeps the id but changes the table name.*/ public static CFMetaData renameTable(CFMetaData cfm, String tableName) { - return new CFMetaData(tableName, cfm.cfName, cfm.cfType, cfm.clockType, cfm.comparator, cfm.subcolumnComparator, cfm.reconciler, cfm.comment, cfm.rowCacheSize, cfm.preloadRowCache, cfm.keyCacheSize, cfm.readRepairChance, cfm.gcGraceSeconds, cfm.defaultValidator, cfm.cfId, cfm.column_metadata); + return new CFMetaData(tableName, + cfm.cfName, + cfm.cfType, + cfm.clockType, + cfm.comparator, + cfm.subcolumnComparator, + cfm.reconciler, + cfm.comment, + cfm.rowCacheSize, + cfm.preloadRowCache, + cfm.keyCacheSize, + cfm.readRepairChance, + cfm.gcGraceSeconds, + cfm.defaultValidator, + cfm.minCompactionThreshold, + cfm.maxCompactionThreshold, + cfm.cfId, + cfm.column_metadata); } /** used for evicting cf data out of static tracking collections. */ @@ -209,6 +289,7 @@ public final class CFMetaData } // a quick and dirty pretty printer for describing the column family... + //TODO: Make it prettier, use it in the CLI public String pretty() { return tableName + "." + cfName + "\n" @@ -236,6 +317,8 @@ public final class CFMetaData cf.read_repair_chance = readRepairChance; cf.gc_grace_seconds = gcGraceSeconds; cf.default_validation_class = new Utf8(defaultValidator.getClass().getName()); + cf.min_compaction_threshold = minCompactionThreshold; + cf.max_compaction_threshold = maxCompactionThreshold; cf.column_metadata = SerDeUtils.createArray(column_metadata.size(), org.apache.cassandra.config.avro.ColumnDef.SCHEMA$); for (ColumnDefinition cd : column_metadata.values()) @@ -269,7 +352,24 @@ public final class CFMetaData ColumnDefinition cd = ColumnDefinition.inflate(aColumn_metadata); column_metadata.put(cd.name, cd); } - return new CFMetaData(cf.keyspace.toString(), cf.name.toString(), ColumnFamilyType.create(cf.column_type.toString()), ClockType.create(cf.clock_type.toString()), comparator, subcolumnComparator, reconciler, cf.comment.toString(), cf.row_cache_size, cf.preload_row_cache, cf.key_cache_size, cf.read_repair_chance, cf.gc_grace_seconds, validator, cf.id, column_metadata); + return new CFMetaData(cf.keyspace.toString(), + cf.name.toString(), + ColumnFamilyType.create(cf.column_type.toString()), + ClockType.create(cf.clock_type.toString()), + comparator, + subcolumnComparator, + reconciler, + cf.comment.toString(), + cf.row_cache_size, + cf.preload_row_cache, + cf.key_cache_size, + cf.read_repair_chance, + cf.gc_grace_seconds, + validator, + cf.min_compaction_threshold, + cf.max_compaction_threshold, + cf.id, + column_metadata); } public boolean equals(Object obj) @@ -297,6 +397,8 @@ public final class CFMetaData .append(keyCacheSize, rhs.keyCacheSize) .append(readRepairChance, rhs.readRepairChance) .append(gcGraceSeconds, rhs.gcGraceSeconds) + .append(minCompactionThreshold, rhs.minCompactionThreshold) + .append(maxCompactionThreshold, rhs.maxCompactionThreshold) .append(cfId.intValue(), rhs.cfId.intValue()) .append(column_metadata, rhs.column_metadata) .isEquals(); @@ -317,6 +419,9 @@ public final class CFMetaData .append(keyCacheSize) .append(readRepairChance) .append(gcGraceSeconds) + .append(defaultValidator) + .append(minCompactionThreshold) + .append(maxCompactionThreshold) .append(cfId) .append(column_metadata) .toHashCode(); @@ -373,8 +478,10 @@ public final class CFMetaData cf_def.key_cache_size, cf_def.read_repair_chance, cf_def.gc_grace_seconds, - DatabaseDescriptor.getComparator(cf_def.default_validation_class == null ? (String)null : cf_def.default_validation_class.toString()), - cfId, + DatabaseDescriptor.getComparator(cf_def.default_validation_class == null ? (String)null : cf_def.default_validation_class.toString()), + cf_def.min_compaction_threshold, + cf_def.max_compaction_threshold, + cfId, column_metadata); } @@ -416,8 +523,10 @@ public final class CFMetaData cf_def.key_cache_size, cf_def.read_repair_chance, cf_def.gc_grace_seconds, - DatabaseDescriptor.getComparator(cf_def.default_validation_class == null ? null : cf_def.default_validation_class), - cfId, + DatabaseDescriptor.getComparator(cf_def.default_validation_class == null ? null : cf_def.default_validation_class), + cf_def.min_compaction_threshold, + cf_def.max_compaction_threshold, + cfId, column_metadata); } @@ -442,6 +551,8 @@ public final class CFMetaData def.setRead_repair_chance(cfm.readRepairChance); def.setGc_grace_seconds(cfm.gcGraceSeconds); def.setDefault_validation_class(cfm.defaultValidator.getClass().getName()); + def.setMin_compaction_threshold(cfm.minCompactionThreshold); + def.setMax_compaction_threshold(cfm.maxCompactionThreshold); List column_meta = new ArrayList< org.apache.cassandra.thrift.ColumnDef>(cfm.column_metadata.size()); for (ColumnDefinition cd : cfm.column_metadata.values()) { @@ -479,6 +590,8 @@ public final class CFMetaData def.read_repair_chance = cfm.readRepairChance; def.gc_grace_seconds = cfm.gcGraceSeconds; def.default_validation_class = cfm.defaultValidator.getClass().getName(); + def.min_compaction_threshold = cfm.minCompactionThreshold; + def.max_compaction_threshold = cfm.maxCompactionThreshold; List column_meta = new ArrayList(cfm.column_metadata.size()); for (ColumnDefinition cd : cfm.column_metadata.values()) { Modified: cassandra/trunk/src/java/org/apache/cassandra/config/DatabaseDescriptor.java URL: http://svn.apache.org/viewvc/cassandra/trunk/src/java/org/apache/cassandra/config/DatabaseDescriptor.java?rev=998558&r1=998557&r2=998558&view=diff ============================================================================== --- cassandra/trunk/src/java/org/apache/cassandra/config/DatabaseDescriptor.java (original) +++ cassandra/trunk/src/java/org/apache/cassandra/config/DatabaseDescriptor.java Sat Sep 18 22:08:06 2010 @@ -586,6 +586,8 @@ public class DatabaseDescriptor cf.read_repair_chance, cf.gc_grace_seconds, default_validator, + cf.min_compaction_threshold, + cf.max_compaction_threshold, metadata); } defs.add(new KSMetaData(keyspace.name, Modified: cassandra/trunk/src/java/org/apache/cassandra/config/RawColumnFamily.java URL: http://svn.apache.org/viewvc/cassandra/trunk/src/java/org/apache/cassandra/config/RawColumnFamily.java?rev=998558&r1=998557&r2=998558&view=diff ============================================================================== --- cassandra/trunk/src/java/org/apache/cassandra/config/RawColumnFamily.java (original) +++ cassandra/trunk/src/java/org/apache/cassandra/config/RawColumnFamily.java Sat Sep 18 22:08:06 2010 @@ -45,5 +45,7 @@ public class RawColumnFamily public boolean preload_row_cache = CFMetaData.DEFAULT_PRELOAD_ROW_CACHE; public int gc_grace_seconds = CFMetaData.DEFAULT_GC_GRACE_SECONDS; public String default_validation_class; + public int min_compaction_threshold = CFMetaData.DEFAULT_MIN_COMPACTION_THRESHOLD; + public int max_compaction_threshold = CFMetaData.DEFAULT_MAX_COMPACTION_THRESHOLD; public RawColumnDefinition[] column_metadata = new RawColumnDefinition[0]; } Modified: cassandra/trunk/src/java/org/apache/cassandra/db/ColumnFamilyStore.java URL: http://svn.apache.org/viewvc/cassandra/trunk/src/java/org/apache/cassandra/db/ColumnFamilyStore.java?rev=998558&r1=998557&r2=998558&view=diff ============================================================================== --- cassandra/trunk/src/java/org/apache/cassandra/db/ColumnFamilyStore.java (original) +++ cassandra/trunk/src/java/org/apache/cassandra/db/ColumnFamilyStore.java Sat Sep 18 22:08:06 2010 @@ -131,13 +131,19 @@ public class ColumnFamilyStore implement private LatencyTracker writeStats = new LatencyTracker(); public final CFMetaData metadata; - + + /* These are locally held copies to be changed from the config during runtime */ + private int minCompactionThreshold; + private int maxCompactionThreshold; + private ColumnFamilyStore(String table, String columnFamilyName, IPartitioner partitioner, int generation, CFMetaData metadata) { assert metadata != null : "null metadata for " + table + ":" + columnFamilyName; this.table = table; columnFamily = columnFamilyName; this.metadata = metadata; + this.minCompactionThreshold = metadata.minCompactionThreshold; + this.maxCompactionThreshold = metadata.maxCompactionThreshold; this.partitioner = partitioner; fileIndexGenerator.set(generation); memtable = new Memtable(this, this.partitioner); @@ -192,6 +198,8 @@ public class ColumnFamilyStore implement 0, CFMetaData.DEFAULT_GC_GRACE_SECONDS, BytesType.instance, + CFMetaData.DEFAULT_MIN_COMPACTION_THRESHOLD, + CFMetaData.DEFAULT_MAX_COMPACTION_THRESHOLD, Collections.emptyMap()); ColumnFamilyStore indexedCfs = ColumnFamilyStore.createColumnFamilyStore(table, indexedCfName, @@ -1603,4 +1611,30 @@ public class ColumnFamilyStore implement ", columnFamily='" + columnFamily + '\'' + ')'; } + + public int getMinimumCompactionThreshold() + { + return minCompactionThreshold; + } + + public void setMinimumCompactionThreshold(int minCompactionThreshold) + { + this.minCompactionThreshold = minCompactionThreshold; + } + + public int getMaximumCompactionThreshold() + { + return maxCompactionThreshold; + } + + public void setMaximumCompactionThreshold(int maxCompactionThreshold) + { + this.maxCompactionThreshold = maxCompactionThreshold; + } + + public void disableAutoCompaction() + { + this.minCompactionThreshold = 0; + this.maxCompactionThreshold = 0; + } } Modified: cassandra/trunk/src/java/org/apache/cassandra/db/ColumnFamilyStoreMBean.java URL: http://svn.apache.org/viewvc/cassandra/trunk/src/java/org/apache/cassandra/db/ColumnFamilyStoreMBean.java?rev=998558&r1=998557&r2=998558&view=diff ============================================================================== --- cassandra/trunk/src/java/org/apache/cassandra/db/ColumnFamilyStoreMBean.java (original) +++ cassandra/trunk/src/java/org/apache/cassandra/db/ColumnFamilyStoreMBean.java Sat Sep 18 22:08:06 2010 @@ -165,4 +165,24 @@ public interface ColumnFamilyStoreMBean public double getBloomFilterFalseRatio(); public double getRecentBloomFilterFalseRatio(); + + /** + * Gets the minimum number of sstables in queue before compaction kicks off + */ + public int getMinimumCompactionThreshold(); + + /** + * Sets the minimum number of sstables in queue before compaction kicks off + */ + public void setMinimumCompactionThreshold(int threshold); + + /** + * Gets the maximum number of sstables in queue before compaction kicks off + */ + public int getMaximumCompactionThreshold(); + + /** + * Sets the maximum number of sstables in queue before compaction kicks off + */ + public void setMaximumCompactionThreshold(int threshold); } Modified: cassandra/trunk/src/java/org/apache/cassandra/db/CompactionManager.java URL: http://svn.apache.org/viewvc/cassandra/trunk/src/java/org/apache/cassandra/db/CompactionManager.java?rev=998558&r1=998557&r2=998558&view=diff ============================================================================== --- cassandra/trunk/src/java/org/apache/cassandra/db/CompactionManager.java (original) +++ cassandra/trunk/src/java/org/apache/cassandra/db/CompactionManager.java Sat Sep 18 22:08:06 2010 @@ -18,7 +18,6 @@ package org.apache.cassandra.db; -import java.io.FileFilter; import java.io.IOException; import java.io.File; import java.lang.management.ManagementFactory; @@ -57,9 +56,6 @@ public class CompactionManager implement private static final Logger logger = LoggerFactory.getLogger(CompactionManager.class); public static final CompactionManager instance; - private int minimumCompactionThreshold = 4; // compact this many sstables min at a time - private int maximumCompactionThreshold = 32; // compact this many sstables max at a time - static { instance = new CompactionManager(); @@ -76,7 +72,7 @@ public class CompactionManager implement private CompactionExecutor executor = new CompactionExecutor(); private Map estimatedCompactions = new NonBlockingHashMap(); - + /** * Call this whenever a compaction might be needed on the given columnfamily. * It's okay to over-call (within reason) since the compactions are single-threaded, @@ -88,7 +84,10 @@ public class CompactionManager implement { public Integer call() throws IOException { - if (minimumCompactionThreshold <= 0 || maximumCompactionThreshold <= 0) + Integer minThreshold = cfs.getMinimumCompactionThreshold(); + Integer maxThreshold = cfs.getMaximumCompactionThreshold(); + + if (minThreshold <= 0 || maxThreshold <= 0) { logger.debug("Compaction is currently disabled."); return 0; @@ -99,12 +98,12 @@ public class CompactionManager implement for (List sstables : buckets) { - if (sstables.size() >= minimumCompactionThreshold) + if (sstables.size() >= minThreshold) { // if we have too many to compact all at once, compact older ones first -- this avoids // re-compacting files we just created. Collections.sort(sstables); - return doCompaction(cfs, sstables.subList(0, Math.min(sstables.size(), maximumCompactionThreshold)), (int) (System.currentTimeMillis() / 1000) - cfs.metadata.gcGraceSeconds); + return doCompaction(cfs, sstables.subList(0, Math.min(sstables.size(), maxThreshold)), (int) (System.currentTimeMillis() / 1000) - cfs.metadata.gcGraceSeconds); } } return 0; @@ -115,12 +114,15 @@ public class CompactionManager implement private void updateEstimateFor(ColumnFamilyStore cfs, Set> buckets) { + Integer minct = cfs.getMinimumCompactionThreshold(); + Integer maxct = cfs.getMaximumCompactionThreshold(); + int n = 0; for (List sstables : buckets) { - if (sstables.size() >= minimumCompactionThreshold) + if (sstables.size() >= minct) { - n += 1 + sstables.size() / (maximumCompactionThreshold - minimumCompactionThreshold); + n += 1 + sstables.size() / (maxct - minct); } } estimatedCompactions.put(cfs, n); @@ -199,42 +201,15 @@ public class CompactionManager implement return executor.submit(callable); } - /** - * Gets the minimum number of sstables in queue before compaction kicks off - */ - public int getMinimumCompactionThreshold() - { - return minimumCompactionThreshold; - } - - /** - * Sets the minimum number of sstables in queue before compaction kicks off - */ - public void setMinimumCompactionThreshold(int threshold) - { - minimumCompactionThreshold = threshold; - } - - /** - * Gets the maximum number of sstables in queue before compaction kicks off - */ - public int getMaximumCompactionThreshold() - { - return maximumCompactionThreshold; - } - - /** - * Sets the maximum number of sstables in queue before compaction kicks off - */ - public void setMaximumCompactionThreshold(int threshold) - { - maximumCompactionThreshold = threshold; - } - + /* Used in tests. */ public void disableAutoCompaction() { - minimumCompactionThreshold = 0; - maximumCompactionThreshold = 0; + for (String ksname : DatabaseDescriptor.getNonSystemTables()) + { + Table ks = Table.open(ksname); + for (ColumnFamilyStore cfs : ks.columnFamilyStores.values()) + cfs.disableAutoCompaction(); + } } /** Modified: cassandra/trunk/src/java/org/apache/cassandra/db/CompactionManagerMBean.java URL: http://svn.apache.org/viewvc/cassandra/trunk/src/java/org/apache/cassandra/db/CompactionManagerMBean.java?rev=998558&r1=998557&r2=998558&view=diff ============================================================================== --- cassandra/trunk/src/java/org/apache/cassandra/db/CompactionManagerMBean.java (original) +++ cassandra/trunk/src/java/org/apache/cassandra/db/CompactionManagerMBean.java Sat Sep 18 22:08:06 2010 @@ -20,25 +20,6 @@ package org.apache.cassandra.db; public interface CompactionManagerMBean { - /** - * Gets the minimum number of sstables in queue before compaction kicks off - */ - public int getMinimumCompactionThreshold(); - - /** - * Sets the minimum number of sstables in queue before compaction kicks off - */ - public void setMinimumCompactionThreshold(int threshold); - - /** - * Gets the maximum number of sstables in queue before compaction kicks off - */ - public int getMaximumCompactionThreshold(); - - /** - * Sets the maximum number of sstables in queue before compaction kicks off - */ - public void setMaximumCompactionThreshold(int threshold); /** * @return the columnfamily currently being compacted; null if none Modified: cassandra/trunk/src/java/org/apache/cassandra/thrift/CassandraServer.java URL: http://svn.apache.org/viewvc/cassandra/trunk/src/java/org/apache/cassandra/thrift/CassandraServer.java?rev=998558&r1=998557&r2=998558&view=diff ============================================================================== --- cassandra/trunk/src/java/org/apache/cassandra/thrift/CassandraServer.java (original) +++ cassandra/trunk/src/java/org/apache/cassandra/thrift/CassandraServer.java Sat Sep 18 22:08:06 2010 @@ -952,6 +952,8 @@ public class CassandraServer implements cf_def.read_repair_chance, cf_def.isSetGc_grace_seconds() ? cf_def.gc_grace_seconds : CFMetaData.DEFAULT_GC_GRACE_SECONDS, DatabaseDescriptor.getComparator(cf_def.default_validation_class), + cf_def.min_compaction_threshold, + cf_def.max_compaction_threshold, ColumnDefinition.fromColumnDef(cf_def.column_metadata)); } Modified: cassandra/trunk/src/java/org/apache/cassandra/tools/NodeCmd.java URL: http://svn.apache.org/viewvc/cassandra/trunk/src/java/org/apache/cassandra/tools/NodeCmd.java?rev=998558&r1=998557&r2=998558&view=diff ============================================================================== --- cassandra/trunk/src/java/org/apache/cassandra/tools/NodeCmd.java (original) +++ cassandra/trunk/src/java/org/apache/cassandra/tools/NodeCmd.java Sat Sep 18 22:08:06 2010 @@ -38,6 +38,7 @@ import java.util.concurrent.ExecutionExc import org.apache.cassandra.cache.JMXInstrumentedCacheMBean; import org.apache.cassandra.concurrent.IExecutorMBean; +import org.apache.cassandra.config.CFMetaData; import org.apache.cassandra.db.ColumnFamilyStoreMBean; import org.apache.cassandra.db.CompactionManager; import org.apache.cassandra.dht.Token; @@ -77,8 +78,8 @@ public class NodeCmd { String header = String.format( "%nAvailable commands: ring, info, version, cleanup, compact, cfstats, snapshot [snapshotname], clearsnapshot, " + "tpstats, flush, drain, repair, decommission, move, loadbalance, removetoken, " + - "setcachecapacity , " + - "getcompactionthreshold, setcompactionthreshold [minthreshold] ([maxthreshold]), " + + "setcachecapacity [keyspace] [cfname] [keycachecapacity] [rowcachecapacity], " + + "getcompactionthreshold [keyspace] [cfname], setcompactionthreshold [cfname] [minthreshold] [maxthreshold], " + "streams [host]"); String usage = String.format("java %s --host %n", NodeCmd.class.getName()); hf.printHelp(usage, "", options, header); @@ -511,9 +512,9 @@ public class NodeCmd { } else if (cmdName.equals("setcachecapacity")) { - if (cmd.getArgs().length != 5) + if (cmd.getArgs().length != 5) // ks cf keycachecap rowcachecap { - System.err.println("cacheinfo requires keyspace and column family name arguments, followed by key cache capacity and row cache capacity, in rows"); + System.err.println("cacheinfo requires: Keyspace name, ColumnFamily name, key cache capacity (in keys), and row cache capacity (in rows)"); } String tableName = cmd.getArgs()[1]; String cfName = cmd.getArgs()[2]; @@ -523,21 +524,33 @@ public class NodeCmd { } else if (cmdName.equals("getcompactionthreshold")) { - probe.getCompactionThreshold(System.out); + if (arguments.length < 3) // ks cf + { + System.err.println("Missing keyspace/cfname"); + printUsage(); + System.exit(1); + } + probe.getCompactionThreshold(System.out, cmd.getArgs()[1], cmd.getArgs()[2]); } else if (cmdName.equals("setcompactionthreshold")) { - if (arguments.length < 2) + if (cmd.getArgs().length != 5) // ks cf min max { - System.err.println("Missing threshold value(s)"); + System.err.println("setcompactionthreshold requires: Keyspace name, ColumnFamily name, " + + "min threshold, and max threshold."); printUsage(); System.exit(1); } - int minthreshold = Integer.parseInt(arguments[1]); - int maxthreshold = CompactionManager.instance.getMaximumCompactionThreshold(); - if (arguments.length > 2) + String ks = cmd.getArgs()[1]; + String cf = cmd.getArgs()[2]; + int minthreshold = Integer.parseInt(arguments[3]); + int maxthreshold = Integer.parseInt(arguments[4]); + + if ((minthreshold < 0) || (maxthreshold < 0)) { - maxthreshold = Integer.parseInt(arguments[2]); + System.err.println("Thresholds must be positive integers."); + printUsage(); + System.exit(1); } if (minthreshold > maxthreshold) @@ -553,10 +566,11 @@ public class NodeCmd { printUsage(); System.exit(1); } - probe.setCompactionThreshold(minthreshold, maxthreshold); + probe.setCompactionThreshold(ks, cf, minthreshold, maxthreshold); } else if (cmdName.equals("streams")) { + // optional host String otherHost = arguments.length > 1 ? arguments[1] : null; nodeCmd.printStreamInfo(otherHost == null ? null : InetAddress.getByName(otherHost), System.out); } Modified: cassandra/trunk/src/java/org/apache/cassandra/tools/NodeProbe.java URL: http://svn.apache.org/viewvc/cassandra/trunk/src/java/org/apache/cassandra/tools/NodeProbe.java?rev=998558&r1=998557&r2=998558&view=diff ============================================================================== --- cassandra/trunk/src/java/org/apache/cassandra/tools/NodeProbe.java (original) +++ cassandra/trunk/src/java/org/apache/cassandra/tools/NodeProbe.java Sat Sep 18 22:08:06 2010 @@ -41,9 +41,6 @@ import org.apache.cassandra.cache.JMXIns import org.apache.cassandra.concurrent.IExecutorMBean; import org.apache.cassandra.config.ConfigurationException; import org.apache.cassandra.db.ColumnFamilyStoreMBean; -import org.apache.cassandra.db.CompactionManager; -import org.apache.cassandra.db.CompactionManagerMBean; -import org.apache.cassandra.dht.Range; import org.apache.cassandra.dht.Token; import org.apache.cassandra.service.StorageServiceMBean; import org.apache.cassandra.streaming.StreamingService; @@ -67,7 +64,6 @@ public class NodeProbe private StorageServiceMBean ssProxy; private MemoryMXBean memProxy; private RuntimeMXBean runtimeProxy; - private CompactionManagerMBean mcmProxy; private StreamingServiceMBean streamProxy; /** @@ -112,8 +108,6 @@ public class NodeProbe { ObjectName name = new ObjectName(ssObjName); ssProxy = JMX.newMBeanProxy(mbeanServerConn, name, StorageServiceMBean.class); - name = new ObjectName(CompactionManager.MBEAN_OBJECT_NAME); - mcmProxy = JMX.newMBeanProxy(mbeanServerConn, name, CompactionManagerMBean.class); name = new ObjectName(StreamingService.MBEAN_OBJECT_NAME); streamProxy = JMX.newMBeanProxy(mbeanServerConn, name, StreamingServiceMBean.class); } catch (MalformedObjectNameException e) @@ -324,10 +318,12 @@ public class NodeProbe * * @param outs the stream to write to */ - public void getCompactionThreshold(PrintStream outs) + public void getCompactionThreshold(PrintStream outs, String ks, String cf) { - outs.println("Current compaction threshold: Min=" + mcmProxy.getMinimumCompactionThreshold() + - ", Max=" + mcmProxy.getMaximumCompactionThreshold()); + ColumnFamilyStoreMBean cfsProxy = getCfsProxy(ks, cf); + outs.println("Current compaction thresholds for " + ks + "/" + cf + ": \n" + + " min = " + cfsProxy.getMinimumCompactionThreshold() + ", " + + " max = " + cfsProxy.getMaximumCompactionThreshold()); } /** @@ -336,13 +332,11 @@ public class NodeProbe * @param minimumCompactionThreshold minimum compaction threshold * @param maximumCompactionThreshold maximum compaction threshold */ - public void setCompactionThreshold(int minimumCompactionThreshold, int maximumCompactionThreshold) + public void setCompactionThreshold(String ks, String cf, int minimumCompactionThreshold, int maximumCompactionThreshold) { - mcmProxy.setMinimumCompactionThreshold(minimumCompactionThreshold); - if (maximumCompactionThreshold >= 0) - { - mcmProxy.setMaximumCompactionThreshold(maximumCompactionThreshold); - } + ColumnFamilyStoreMBean cfsProxy = getCfsProxy(ks, cf); + cfsProxy.setMinimumCompactionThreshold(minimumCompactionThreshold); + cfsProxy.setMaximumCompactionThreshold(maximumCompactionThreshold); } public void setCacheCapacities(String tableName, String cfName, int keyCacheCapacity, int rowCacheCapacity) @@ -425,6 +419,20 @@ public class NodeProbe { return ssProxy.exportSchema(); } + + private ColumnFamilyStoreMBean getCfsProxy(String ks, String cf) { + ColumnFamilyStoreMBean cfsProxy = null; + try { + cfsProxy = JMX.newMBeanProxy(mbeanServerConn, + new ObjectName("org.apache.cassandra.db:type=ColumnFamilies,keyspace="+ks+",columnfamily="+cf), + ColumnFamilyStoreMBean.class); + } + catch (MalformedObjectNameException mone) { + System.err.println("ColumnFamilyStore for " + ks + "/" + cf + " not found."); + System.exit(1); + } + return cfsProxy; + } } class ColumnFamilyStoreMBeanIterator implements Iterator> Modified: cassandra/trunk/test/unit/org/apache/cassandra/db/DefsTest.java URL: http://svn.apache.org/viewvc/cassandra/trunk/test/unit/org/apache/cassandra/db/DefsTest.java?rev=998558&r1=998557&r2=998558&view=diff ============================================================================== --- cassandra/trunk/test/unit/org/apache/cassandra/db/DefsTest.java (original) +++ cassandra/trunk/test/unit/org/apache/cassandra/db/DefsTest.java Sat Sep 18 22:08:06 2010 @@ -85,7 +85,7 @@ public class DefsTest extends CleanupHel @Test public void addNewCfToBogusTable() throws InterruptedException { - CFMetaData newCf = new CFMetaData("MadeUpKeyspace", "NewCF", ColumnFamilyType.Standard, ClockType.Timestamp, UTF8Type.instance, null, TimestampReconciler.instance, "new cf", 0, false, 1.0, 0, 864000, BytesType.instance, Collections.emptyMap()); + CFMetaData newCf = addTestCF("MadeUpKeyspace", "NewCF", "new cf"); try { new AddColumnFamily(newCf).apply(); @@ -110,7 +110,8 @@ public class DefsTest extends CleanupHel assert DatabaseDescriptor.getDefsVersion().equals(prior); // add a cf. - CFMetaData newCf1 = new CFMetaData("Keyspace1", "MigrationCf_1", ColumnFamilyType.Standard, ClockType.Timestamp, UTF8Type.instance, null, TimestampReconciler.instance, "Migration CF ", 0, false, 1.0, 0, 864000, BytesType.instance, Collections.emptyMap()); + CFMetaData newCf1 = addTestCF("Keyspace1", "MigrationCf_1", "Migration CF"); + Migration m1 = new AddColumnFamily(newCf1); m1.apply(); UUID ver1 = m1.getVersion(); @@ -160,7 +161,8 @@ public class DefsTest extends CleanupHel final String cf = "BrandNewCf"; KSMetaData original = DatabaseDescriptor.getTableDefinition(ks); - CFMetaData newCf = new CFMetaData(original.name, cf, ColumnFamilyType.Standard, ClockType.Timestamp, UTF8Type.instance, null, TimestampReconciler.instance, "A New Column Family", 0, false, 1.0, 0, 864000, BytesType.instance, Collections.emptyMap()); + CFMetaData newCf = addTestCF(original.name, cf, "A New Column Family"); + assert !DatabaseDescriptor.getTableDefinition(ks).cfMetaData().containsKey(newCf.cfName); new AddColumnFamily(newCf).apply(); @@ -279,7 +281,8 @@ public class DefsTest extends CleanupHel public void addNewKS() throws ConfigurationException, IOException, ExecutionException, InterruptedException { DecoratedKey dk = Util.dk("key0"); - CFMetaData newCf = new CFMetaData("NewKeyspace1", "AddedStandard1", ColumnFamilyType.Standard, ClockType.Timestamp, UTF8Type.instance, null, TimestampReconciler.instance, "A new cf for a new ks", 0, false, 1.0, 0, 864000, BytesType.instance, Collections.emptyMap()); + CFMetaData newCf = addTestCF("NewKeyspace1", "AddedStandard1", "A new cf for a new ks"); + KSMetaData newKs = new KSMetaData(newCf.tableName, SimpleStrategy.class, null, 5, newCf); new AddKeyspace(newKs).apply(); @@ -436,7 +439,7 @@ public class DefsTest extends CleanupHel new AddKeyspace(newKs).apply(); assert DatabaseDescriptor.getTableDefinition("EmptyKeyspace") != null; - CFMetaData newCf = new CFMetaData("EmptyKeyspace", "AddedLater", ColumnFamilyType.Standard, ClockType.Timestamp, UTF8Type.instance, null, TimestampReconciler.instance, "A new CF to add to an empty KS", 0, false, 1.0, 0, 864000, BytesType.instance, Collections.emptyMap()); + CFMetaData newCf = addTestCF("EmptyKeyspace", "AddedLater", "A new CF to add to an empty KS"); //should not exist until apply assert !DatabaseDescriptor.getTableDefinition(newKs.name).cfMetaData().containsKey(newCf.cfName); @@ -466,7 +469,7 @@ public class DefsTest extends CleanupHel public void testUpdateKeyspace() throws ConfigurationException, IOException, ExecutionException, InterruptedException { // create a keyspace to serve as existing. - CFMetaData cf = new CFMetaData("UpdatedKeyspace", "AddedStandard1", ColumnFamilyType.Standard, ClockType.Timestamp, UTF8Type.instance, null, TimestampReconciler.instance, "A new cf for a new ks", 0, false, 1.0, 0, 864000, BytesType.instance, Collections.emptyMap()); + CFMetaData cf = addTestCF("UpdatedKeyspace", "AddedStandard1", "A new cf for a new ks"); KSMetaData oldKs = new KSMetaData(cf.tableName, SimpleStrategy.class, null, 5, cf); new AddKeyspace(oldKs).apply(); @@ -475,7 +478,7 @@ public class DefsTest extends CleanupHel assert DatabaseDescriptor.getTableDefinition(cf.tableName) == oldKs; // anything with cf defs should fail. - CFMetaData cf2 = new CFMetaData(cf.tableName, "AddedStandard2", ColumnFamilyType.Standard, ClockType.Timestamp, UTF8Type.instance, null, TimestampReconciler.instance, "A new cf for a new ks", 0, false, 1.0, 0, 864000, BytesType.instance, Collections.emptyMap()); + CFMetaData cf2 = addTestCF(cf.tableName, "AddedStandard2", "A new cf for a new ks"); KSMetaData newBadKs = new KSMetaData(cf.tableName, SimpleStrategy.class, null, 4, cf2); try { @@ -508,12 +511,12 @@ public class DefsTest extends CleanupHel assert newFetchedKs.strategyClass.equals(newKs.strategyClass); assert !newFetchedKs.strategyClass.equals(oldKs.strategyClass); } - + @Test public void testUpdateColumnFamilyNoIndexes() throws ConfigurationException, IOException, ExecutionException, InterruptedException { // create a keyspace with a cf to update. - CFMetaData cf = new CFMetaData("UpdatedCfKs", "Standard1added", ColumnFamilyType.Standard, ClockType.Timestamp, UTF8Type.instance, null, TimestampReconciler.instance, "A new cf that will be updated", 0, false, 1.0, 0, 864000, BytesType.instance, Collections.emptyMap()); + CFMetaData cf = addTestCF("UpdatedCfKs", "Standard1added", "A new cf that will be updated"); KSMetaData ksm = new KSMetaData(cf.tableName, SimpleStrategy.class, null, 1, cf); new AddKeyspace(ksm).apply(); @@ -646,4 +649,26 @@ public class DefsTest extends CleanupHel cf_def.setComparator_type(UTF8Type.class.getSimpleName()); } } + + private CFMetaData addTestCF(String ks, String cf, String comment) + { + return new CFMetaData(ks, + cf, + ColumnFamilyType.Standard, + ClockType.Timestamp, + UTF8Type.instance, + null, + TimestampReconciler.instance, + comment, + 0, + false, + 1.0, + 0, + 864000, + BytesType.instance, + 4, + 32, + Collections.emptyMap()); + } + }