3333import com .google .inject .Inject ;
3434import org .apache .druid .common .utils .IdUtils ;
3535import org .apache .druid .error .DruidException ;
36+ import org .apache .druid .error .InternalServerError ;
3637import org .apache .druid .error .InvalidInput ;
3738import org .apache .druid .indexing .overlord .DataSourceMetadata ;
3839import org .apache .druid .indexing .overlord .IndexerMetadataStorageCoordinator ;
@@ -1419,7 +1420,7 @@ private PendingSegmentRecord createNewPendingSegment(
14191420 version ,
14201421 partialShardSpec .complete (jsonMapper , newPartitionId , 0 )
14211422 );
1422- pendingSegmentId = getTrueAllocatedId (transaction , pendingSegmentId );
1423+ pendingSegmentId = getUniqueIdForPrimaryAllocation (transaction , pendingSegmentId );
14231424 return PendingSegmentRecord .create (
14241425 pendingSegmentId ,
14251426 request .getSequenceName (),
@@ -1457,7 +1458,7 @@ private PendingSegmentRecord createNewPendingSegment(
14571458 )
14581459 );
14591460 return PendingSegmentRecord .create (
1460- getTrueAllocatedId (transaction , pendingSegmentId ),
1461+ getUniqueIdForSecondaryAllocation (transaction , pendingSegmentId ),
14611462 request .getSequenceName (),
14621463 request .getPreviousSegmentId (),
14631464 null ,
@@ -1562,7 +1563,7 @@ private SegmentIdWithShardSpec createNewPendingSegment(
15621563 version ,
15631564 partialShardSpec .complete (jsonMapper , newPartitionId , 0 )
15641565 );
1565- return getTrueAllocatedId (transaction , allocatedId );
1566+ return getUniqueIdForPrimaryAllocation (transaction , allocatedId );
15661567 } else if (!overallMaxId .getInterval ().equals (interval )) {
15671568 log .warn (
15681569 "Cannot allocate new segment for dataSource[%s], interval[%s], existingVersion[%s]: conflicting segment[%s]." ,
@@ -1590,18 +1591,90 @@ private SegmentIdWithShardSpec createNewPendingSegment(
15901591 committedMaxId == null ? 0 : committedMaxId .getShardSpec ().getNumCorePartitions ()
15911592 )
15921593 );
1593- return getTrueAllocatedId (transaction , allocatedId );
1594+ return getUniqueIdForSecondaryAllocation (transaction , allocatedId );
15941595 }
15951596 }
15961597
15971598 /**
1598- * Verifies that the allocated id doesn't already exist in the druid_segments table.
1599- * If yes, try to get the max unallocated id considering the unused segments for the datasource, version and interval
1600- * Otherwise, use the same id.
1601- * @param allocatedId The segment allcoted on the basis of used and pending segments
1602- * @return a segment id that isn't already used by other unused segments
1599+ * Returns a unique {@link SegmentIdWithShardSpec} which does not clash with
1600+ * any existing unused segment. If an unused segment already exists that matches
1601+ * the interval and version of the given {@code allocatedId}, a fresh version
1602+ * is created by suffixing one or more {@link PendingSegmentRecord#CONCURRENT_APPEND_VERSION_SUFFIX}.
1603+ * Such a conflict can happen only if all the segments in this interval created
1604+ * by a prior APPEND task were marked as unused.
1605+ * <p>
1606+ * This method should be called only when allocating the first segment in an interval.
1607+ */
1608+ private SegmentIdWithShardSpec getUniqueIdForPrimaryAllocation (
1609+ SegmentMetadataTransaction transaction ,
1610+ SegmentIdWithShardSpec allocatedId
1611+ )
1612+ {
1613+ // Get all the unused segment versions for this datasource and interval
1614+ final Set <String > unusedSegmentVersions = transaction .noCacheSql ().retrieveUnusedSegmentVersionsWithInterval (
1615+ allocatedId .getDataSource (),
1616+ allocatedId .getInterval ()
1617+ );
1618+
1619+ final String allocatedVersion = allocatedId .getVersion ();
1620+ if (!unusedSegmentVersions .contains (allocatedVersion )) {
1621+ // Nothing to do, this version is new
1622+ return allocatedId ;
1623+ } else if (!PendingSegmentRecord .DEFAULT_VERSION_FOR_CONCURRENT_APPEND .equals (allocatedVersion )) {
1624+ // Version clash should never happen for non-APPEND locks
1625+ throw DruidException .defensive (
1626+ "Cannot allocate segment[%s] as there are already some unused segments"
1627+ + " for version[%s] in this interval." ,
1628+ allocatedId , allocatedVersion
1629+ );
1630+ }
1631+
1632+ // Iterate until a new non-clashing version is found
1633+ boolean foundFreshVersion = false ;
1634+ StringBuilder candidateVersion = new StringBuilder (allocatedId .getVersion ());
1635+ for (int i = 0 ; i < 10 ; ++i ) {
1636+ if (unusedSegmentVersions .contains (candidateVersion .toString ())) {
1637+ candidateVersion .append (PendingSegmentRecord .CONCURRENT_APPEND_VERSION_SUFFIX );
1638+ } else {
1639+ foundFreshVersion = true ;
1640+ break ;
1641+ }
1642+ }
1643+
1644+ if (foundFreshVersion ) {
1645+ final SegmentIdWithShardSpec uniqueId = new SegmentIdWithShardSpec (
1646+ allocatedId .getDataSource (),
1647+ allocatedId .getInterval (),
1648+ candidateVersion .toString (),
1649+ allocatedId .getShardSpec ()
1650+ );
1651+ log .info (
1652+ "Created new unique pending segment ID[%s] with version[%s] for originally allocated ID[%s]." ,
1653+ uniqueId , candidateVersion .toString (), allocatedId
1654+ );
1655+
1656+ return uniqueId ;
1657+ } else {
1658+ throw InternalServerError .exception (
1659+ "Could not allocate segment[%s] as there are too many clashing unused"
1660+ + " versions(upto [%s]) in the interval. Kill the old unused versions to proceed." ,
1661+ allocatedId , candidateVersion .toString ()
1662+ );
1663+ }
1664+ }
1665+
1666+ /**
1667+ * Returns a unique {@link SegmentIdWithShardSpec} which does not clash with
1668+ * any existing unused segment. If an unused segment already exists that matches
1669+ * the interval, version and partition number of the given {@code allocatedId},
1670+ * a higher partition number is used. Such a conflict can happen only if some
1671+ * segments of the underlying version have been marked as unused while others
1672+ * are still used.
1673+ * <p>
1674+ * This method should not be called when allocating the first segment in an
1675+ * interval.
16031676 */
1604- private SegmentIdWithShardSpec getTrueAllocatedId (
1677+ private SegmentIdWithShardSpec getUniqueIdForSecondaryAllocation (
16051678 SegmentMetadataTransaction transaction ,
16061679 SegmentIdWithShardSpec allocatedId
16071680 )
@@ -1631,7 +1704,7 @@ private SegmentIdWithShardSpec getTrueAllocatedId(
16311704 allocatedId .getShardSpec ().getPartitionNum (),
16321705 unusedMaxId .getPartitionNum () + 1
16331706 );
1634- return new SegmentIdWithShardSpec (
1707+ final SegmentIdWithShardSpec uniqueId = new SegmentIdWithShardSpec (
16351708 allocatedId .getDataSource (),
16361709 allocatedId .getInterval (),
16371710 allocatedId .getVersion (),
@@ -1640,6 +1713,12 @@ private SegmentIdWithShardSpec getTrueAllocatedId(
16401713 allocatedId .getShardSpec ().getNumCorePartitions ()
16411714 )
16421715 );
1716+ log .info (
1717+ "Created new unique pending segment ID[%s] with partition number[%s] for originally allocated ID[%s]." ,
1718+ uniqueId , maxPartitionNum , allocatedId
1719+ );
1720+
1721+ return uniqueId ;
16431722 }
16441723
16451724 @ Override
0 commit comments